From a4638200b5779cdc1d6c1f1830f1a530f1f2c934 Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 25 Sep 2024 01:02:37 +0800 Subject: [PATCH 1/3] Update Update to RELEASE_2024 --- .../OpenSwiftUI/Core/Render/DisplayLink.swift | 2 +- Sources/OpenSwiftUI/Core/Update/Update.swift | 126 ------------ .../Core/View/ViewRendererHost.swift | 6 +- .../Integration/UIKit/UIHostingView.swift | 4 +- Sources/OpenSwiftUI/View/Toggle/Switch.swift | 2 +- Sources/OpenSwiftUICore/Data/Update.swift | 179 ++++++++++++++++++ Sources/OpenSwiftUICore/Log/Signpost.swift | 2 +- 7 files changed, 187 insertions(+), 134 deletions(-) delete mode 100644 Sources/OpenSwiftUI/Core/Update/Update.swift create mode 100644 Sources/OpenSwiftUICore/Data/Update.swift diff --git a/Sources/OpenSwiftUI/Core/Render/DisplayLink.swift b/Sources/OpenSwiftUI/Core/Render/DisplayLink.swift index bc3f09ae..ea882269 100644 --- a/Sources/OpenSwiftUI/Core/Render/DisplayLink.swift +++ b/Sources/OpenSwiftUI/Core/Render/DisplayLink.swift @@ -43,7 +43,7 @@ final class DisplayLink: NSObject { } func invalidate() { - Update.lock.withLock { + Update.locked { // TODO } } diff --git a/Sources/OpenSwiftUI/Core/Update/Update.swift b/Sources/OpenSwiftUI/Core/Update/Update.swift deleted file mode 100644 index 8f469377..00000000 --- a/Sources/OpenSwiftUI/Core/Update/Update.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// Update.swift -// OpenSwiftUI -// -// Status: WIP -// ID: EA173074DA35FA471DC70643259B7E74 - -internal import COpenSwiftUI -internal import OpenGraphShims -import Foundation - -extension MovableLock { - @inline(__always) - func withLock(_ body: () throws -> R) rethrows -> R { - lock() - defer { unlock() } - return try body() - } -} - -enum Update { - static let trackHost: AnyObject = TraceHost() - static let lock = MovableLock.create() - private static var depth = 0 - private static var actions: [() -> Void] = [] - - static func begin() { - lock.lock() - depth += 1 - if depth == 1 { - guard Signpost.viewHost.isEnabled else { - return - } - // TODO: Signpost - } - } - - static func end() { - if depth == 1 { - dispatchActions() - // TODO: Signpost - } - depth -= 1 - lock.unlock() - } - - @inline(__always) - static func perform(_ body: () -> Value) -> Value { - begin() - defer { end() } - return body() - } - - static func enqueueAction(_ action: @escaping () -> Void) { - begin() - actions.append(action) - end() - } - - static func ensure(_ body: () throws -> Value) rethrows -> Value { - try lock.withLock { - if depth == 0 { - begin() - defer { end() } - return try body() - } else { - return try body() - } - } - } - - @inline(__always) - static func dispatchActions() { - guard !actions.isEmpty else { - return - } - - let actions = Update.actions - Update.actions = [] - onMainThread { - // TODO: Signpost.postUpdateActions - begin() - for action in actions { - let oldDepth = depth - action() - let newDepth = depth - if newDepth != oldDepth { - fatalError("Action caused unbalanced updates.") - } - } - end() - } - } - - @inline(__always) - static func syncMain(_ body: () -> Void) { - #if os(WASI) - // FIXME: See #76 - body() - #else - if Thread.isMainThread { - body() - } else { - withoutActuallyEscaping(body) { escapableBody in - MovableLock.syncMain(lock: lock) { - #if canImport(Darwin) - AnyRuleContext(attribute: AnyOptionalAttribute.current.identifier).update(body: escapableBody) - #else - fatalError("See #39") - #endif - } - } - } - #endif - } -} - -extension Update { - private class TraceHost {} -} - -// FIXME: migrate to use @_extern(c, "xx") in Swift 6 -extension MovableLock { - @_silgen_name("_MovableLockSyncMain") - static func syncMain(lock: MovableLock, body: @escaping () -> Void) -} diff --git a/Sources/OpenSwiftUI/Core/View/ViewRendererHost.swift b/Sources/OpenSwiftUI/Core/View/ViewRendererHost.swift index 778aac01..fa238931 100644 --- a/Sources/OpenSwiftUI/Core/View/ViewRendererHost.swift +++ b/Sources/OpenSwiftUI/Core/View/ViewRendererHost.swift @@ -21,7 +21,7 @@ protocol ViewRendererHost: ViewGraphDelegate { extension ViewRendererHost { func updateViewGraph(body: (ViewGraph) -> Value) -> Value { - Update.perform { + Update.dispatchImmediately { OGGraph.withoutUpdate { updateGraph() return body(viewGraph) @@ -37,7 +37,7 @@ extension ViewRendererHost { } func invalidateProperties(_ properties: ViewRendererHostProperties, mayDeferUpdate: Bool) { - Update.lock.withLock { + Update.locked { guard !propertiesNeedingUpdate.contains(properties) else { return } @@ -56,7 +56,7 @@ extension ViewRendererHost { } func render(interval: Double, updateDisplayList: Bool = true) { - Update.perform { + Update.dispatchImmediately { guard !isRendering else { return } diff --git a/Sources/OpenSwiftUI/Integration/UIKit/UIHostingView.swift b/Sources/OpenSwiftUI/Integration/UIKit/UIHostingView.swift index a5067696..bf313dfa 100644 --- a/Sources/OpenSwiftUI/Integration/UIKit/UIHostingView.swift +++ b/Sources/OpenSwiftUI/Integration/UIKit/UIHostingView.swift @@ -96,7 +96,7 @@ open class _UIHostingView: UIView where Content: View { guard canAdvanceTimeAutomatically else { return } - Update.lock.withLock { + Update.locked { cancelAsyncRendering() let interval: Double if let displayLink, displayLink.willRender { @@ -129,7 +129,7 @@ open class _UIHostingView: UIView where Content: View { } func cancelAsyncRendering() { - Update.lock.withLock { + Update.locked { displayLink?.cancelAsyncRendering() } } diff --git a/Sources/OpenSwiftUI/View/Toggle/Switch.swift b/Sources/OpenSwiftUI/View/Toggle/Switch.swift index 9732e55c..56c727f6 100644 --- a/Sources/OpenSwiftUI/View/Toggle/Switch.swift +++ b/Sources/OpenSwiftUI/View/Toggle/Switch.swift @@ -73,7 +73,7 @@ private class PlatformSwitchCoordinator: PlatformViewCoordinator { @objc func isOnChanged(_ sender: UISwitch) { - Update.perform { + Update.dispatchImmediately { _isOn.wrappedValue = sender.isOn } sender.setOn(_isOn.wrappedValue, animated: true) diff --git a/Sources/OpenSwiftUICore/Data/Update.swift b/Sources/OpenSwiftUICore/Data/Update.swift new file mode 100644 index 00000000..2579e445 --- /dev/null +++ b/Sources/OpenSwiftUICore/Data/Update.swift @@ -0,0 +1,179 @@ +// +// Update.swift +// OpenSwiftUI +// +// Audited for RELEASE_2024 +// Status: Blocked by Signpost +// ID: EA173074DA35FA471DC70643259B7E74 (RELEASE_2021) +// ID: 61534957AEEC2EDC447ABDC13B4D426F (RELEASE_2024) + +internal import COpenSwiftUICore +internal import OpenGraphShims +import Foundation + +package enum Update { + private final class TraceHost {} + static let trackHost: AnyObject = TraceHost() + + private static var depth = 0 + private static var dispatchDepth = 0 + private static let _lock = MovableLock.create() + private static var actions: [() -> Void] = [] + private static let lockAssertionsAreEnabled = EnvironmentHelper.bool(for: "OPENSWIFTUI_ASSERT_LOCKS") + + @inlinable + package static var isActive: Bool { + depth != 0 + } + + @inlinable + package static var threadIsUpdating: Bool { + depth < dispatchDepth ? isOwner : false + } + + @inlinable + package static func assertIsActive() { + assert(isActive) + } + + package static func lock() { + _lock.lock() + } + package static func unlock() { + _lock.unlock() + } + + package static var isOwner: Bool { + _lock.isOwner() + } + + package static func wait() { + _lock.wait() + } + + package static func broadcast() { + _lock.broadcast() + } + + package static func assertIsLocked() { + guard lockAssertionsAreEnabled else { + return + } + guard isOwner else { + fatalError("OpenSwiftUI is active without having taken its own lock - missing Update.ensure()?") + } + } + + package static func begin() { + lock() + depth += 1 + if depth == 1 { + // TODO: Signpost.renderUpdate.trace + trackHost + } + } + + package static func end() { + if depth == 1 { + dispatchActions() + // TODO: Signpost.renderUpdate.trace + trackHost + } + depth -= 1 + unlock() + } + + package static func enqueueAction(_ action: @escaping () -> Void) { + begin() + actions.append(action) + end() + } + + @inlinable + @inline(__always) + package static func locked(_ body: () throws -> T) rethrows -> T { + lock() + defer { unlock() } + return try body() + } + + package static func syncMain(_ body: () -> Void) { + #if os(WASI) + // FIXME: See #76 + body() + #else + if Thread.isMainThread { + body() + } else { + withoutActuallyEscaping(body) { escapableBody in + let context = AnyRuleContext(attribute: AnyOptionalAttribute.current.identifier) + MovableLock.syncMain(lock: _lock) { + #if canImport(Darwin) + context.update(body: escapableBody) + #else + fatalError("See #39") + #endif + } + } + } + #endif + } + + package static func ensure(_ callback: () throws -> T) rethrows -> T { + try locked { + begin() + defer { end() } + return try callback() + } + } + + package static var canDispatch: Bool { + assertIsLocked() + guard depth == 1 else { + return false + } + return !actions.isEmpty + } + + package static func dispatchActions() { + guard canDispatch else { return } + repeat { + let actions = Update.actions + Update.actions = [] + onMainThread { + Signpost.postUpdateActions.traceInterval(object: trackHost, nil) { + begin() + let oldDispatchDepth = dispatchDepth + let oldDepth = depth + dispatchDepth = oldDepth + defer { + dispatchDepth = oldDispatchDepth + end() + } + for action in actions { + action() + let newDepth = depth + guard newDepth == oldDepth else { + fatalError("Action caused unbalanced updates.") + } + } + } + } + } while(!Update.actions.isEmpty) + } + + package static func dispatchImmediately(_ body: () -> T) -> T { + begin() + let oldDispatchDepth = dispatchDepth + dispatchDepth = depth + defer { + dispatchDepth = oldDispatchDepth + end() + } + return body() + } +} + +// FIXME: migrate to use @_extern(c, "xx") in Swift 6 +extension MovableLock { + @_silgen_name("_MovableLockSyncMain") + static func syncMain(lock: MovableLock, body: @escaping () -> Void) +} diff --git a/Sources/OpenSwiftUICore/Log/Signpost.swift b/Sources/OpenSwiftUICore/Log/Signpost.swift index 69128623..2c531c12 100644 --- a/Sources/OpenSwiftUICore/Log/Signpost.swift +++ b/Sources/OpenSwiftUICore/Log/Signpost.swift @@ -2,7 +2,7 @@ // Signpost.swift // OpenSwiftUI // -// Audited for RELEASE_2021 +// Audited for RELEASE_2024 // Status: WIP // ID: 34756F646CF7AC3DBE2A8E0B344C962F (RELEASE_2021) // ID: 59349949219F590F26B6A55CEC9D59A2 (RELEASE_2024) From 18bd3873421cd39609c4f3819d6c7de75186978e Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 25 Sep 2024 01:06:31 +0800 Subject: [PATCH 2/3] Optimize MovableLock API interface --- Sources/COpenSwiftUICore/include/MovableLock.h | 4 ++-- Sources/OpenSwiftUICore/Data/Update.swift | 2 +- .../COpenSwiftUI/MovableLockTests.swift | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/COpenSwiftUICore/include/MovableLock.h b/Sources/COpenSwiftUICore/include/MovableLock.h index 0cf1dde8..f777dffe 100644 --- a/Sources/COpenSwiftUICore/include/MovableLock.h +++ b/Sources/COpenSwiftUICore/include/MovableLock.h @@ -37,8 +37,8 @@ MovableLock _MovableLockCreate(void) OPENSWIFTUI_SWIFT_NAME(MovableLock.create() OPENSWIFTUI_EXPORT OPENSWIFTUI_REFINED_FOR_SWIFT void _MovableLockDestory(MovableLock lock) OPENSWIFTUI_SWIFT_NAME(MovableLock.destory(self:)); -bool _MovableLockIsOwner(MovableLock lock) OPENSWIFTUI_SWIFT_NAME(MovableLock.isOwner(self:)); -bool _MovableLockIsOuterMostOwner(MovableLock lock) OPENSWIFTUI_SWIFT_NAME(MovableLock.isOuterMostOwner(self:)); +bool _MovableLockIsOwner(MovableLock lock) OPENSWIFTUI_SWIFT_NAME(getter:MovableLock.isOwner(self:)); +bool _MovableLockIsOuterMostOwner(MovableLock lock) OPENSWIFTUI_SWIFT_NAME(getter:MovableLock.isOuterMostOwner(self:)); void _MovableLockLock(MovableLock lock) OPENSWIFTUI_SWIFT_NAME(MovableLock.lock(self:)); void _MovableLockUnlock(MovableLock lock) OPENSWIFTUI_SWIFT_NAME(MovableLock.unlock(self:)); void _MovableLockSyncMain(MovableLock lock, const void *context, void (*function)(const void *context)) OPENSWIFTUI_SWIFT_NAME(MovableLock.syncMain(self:_:function:)); diff --git a/Sources/OpenSwiftUICore/Data/Update.swift b/Sources/OpenSwiftUICore/Data/Update.swift index 2579e445..565a3452 100644 --- a/Sources/OpenSwiftUICore/Data/Update.swift +++ b/Sources/OpenSwiftUICore/Data/Update.swift @@ -44,7 +44,7 @@ package enum Update { } package static var isOwner: Bool { - _lock.isOwner() + _lock.isOwner } package static func wait() { diff --git a/Tests/OpenSwiftUITests/COpenSwiftUI/MovableLockTests.swift b/Tests/OpenSwiftUITests/COpenSwiftUI/MovableLockTests.swift index 1d14c65b..2cf6e467 100644 --- a/Tests/OpenSwiftUITests/COpenSwiftUI/MovableLockTests.swift +++ b/Tests/OpenSwiftUITests/COpenSwiftUI/MovableLockTests.swift @@ -22,14 +22,14 @@ final class MovableLockTests { @Test func owner() { - #expect(lock.isOwner() == false) - #expect(lock.isOuterMostOwner() == false) + #expect(lock.isOwner == false) + #expect(lock.isOuterMostOwner == false) lock.lock() - #expect(lock.isOwner() == true) - #expect(lock.isOuterMostOwner() == true) + #expect(lock.isOwner == true) + #expect(lock.isOuterMostOwner == true) lock.unlock() - #expect(lock.isOwner() == false) - #expect(lock.isOuterMostOwner() == false) + #expect(lock.isOwner == false) + #expect(lock.isOuterMostOwner == false) } } #endif From e5f871f27e66d67a777bf3fa52e15015e0d54cdf Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 25 Sep 2024 01:41:06 +0800 Subject: [PATCH 3/3] Add UpdateTests --- .../Data/UpdateTests.swift | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Tests/OpenSwiftUICoreTests/Data/UpdateTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/UpdateTests.swift b/Tests/OpenSwiftUICoreTests/Data/UpdateTests.swift new file mode 100644 index 00000000..f7029fa5 --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Data/UpdateTests.swift @@ -0,0 +1,39 @@ +// +// UpdateTests.swift +// OpenSwiftUICoreTests + +import OpenSwiftUICore +import Testing + +struct UpdateTests { + @Test + @MainActor + func example() async { + await confirmation(expectedCount: 4) { confirmation in + Update.locked { + confirmation() + #expect(!Update.isActive) + #expect(!Update.threadIsUpdating) + #expect(Update.isOwner) + Update.dispatchImmediately { + confirmation() + #expect(Update.isActive) + #expect(!Update.threadIsUpdating) + } + var result = 0 + Update.enqueueAction { + confirmation() + result += 2 + #expect(result == 2) + } + Update.enqueueAction { + confirmation() + result += 3 + #expect(result == 5) + } + Update.dispatchActions() + #expect(result == 5) + } + } + } +}