From 4af869e8d38cf195de0cd3cd64c2cdda41a4a7b2 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 6 May 2025 21:27:30 -0700 Subject: [PATCH] macOS: trailing closures for FlutterRunLoop.perform* (#168415) Support trailing closure syntax in FlutterRunLoop perform methods. This allows us to write Swift code like: ``` FlutterRunLoop.mainLoop.perform(withDelay: delay) { // code. } ``` This also matches the ordering used in NSRunLoop/RunLoop: * https://developer.apple.com/documentation/foundation/runloop/perform(inmodes:block:)?language=objc * https://developer.apple.com/documentation/foundation/runloop/perform(inmodes:block:) Follow-up to flutter/flutter#168174. No tests since this is just a method rename. Issue: https://github.com/flutter/flutter/issues/144791 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../macos/framework/Source/FlutterEngine.mm | 20 +++++------ .../Source/FlutterResizeSynchronizer.mm | 13 ++++--- .../framework/Source/FlutterRunLoop.swift | 7 ++-- .../framework/Source/FlutterVSyncWaiter.mm | 35 +++++++++---------- 4 files changed, 36 insertions(+), 39 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index e60041d0232..325a003c5df 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -1565,16 +1565,16 @@ static void SetThreadPriority(FlutterThreadPriority priority) { const auto engine_time = _embedderAPI.GetCurrentTime(); [FlutterRunLoop.mainRunLoop - performBlock:^{ - FlutterEngine* self = weakSelf; - if (self != nil && self->_engine != nil) { - auto result = _embedderAPI.RunTask(self->_engine, &task); - if (result != kSuccess) { - NSLog(@"Could not post a task to the Flutter engine."); - } - } - } - afterDelay:(targetTime - (double)engine_time) / NSEC_PER_SEC]; + performAfterDelay:(targetTime - (double)engine_time) / NSEC_PER_SEC + block:^{ + FlutterEngine* self = weakSelf; + if (self != nil && self->_engine != nil) { + auto result = _embedderAPI.RunTask(self->_engine, &task); + if (result != kSuccess) { + NSLog(@"Could not post a task to the Flutter engine."); + } + } + }]; } // Getter used by test harness, only exposed through the FlutterEngine(Test) category diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm index 1586932b5df..a3da29192e0 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm @@ -45,13 +45,12 @@ if (_inResize) { delay = 0; } - [FlutterRunLoop.mainRunLoop - performBlock:^{ - _didReceiveFrame = YES; - _contentSize = size; - notify(); - } - afterDelay:delay]; + [FlutterRunLoop.mainRunLoop performAfterDelay:delay + block:^{ + _didReceiveFrame = YES; + _contentSize = size; + notify(); + }]; } - (void)shutDown { diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterRunLoop.swift b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterRunLoop.swift index 74cf15667f2..e99a563934e 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterRunLoop.swift +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterRunLoop.swift @@ -107,8 +107,7 @@ import Foundation } // Schedules a block to be executed on the main thread. - @objc(performBlock:afterDelay:) - public func perform(block: @escaping () -> Void, afterDelay delay: TimeInterval) { + @objc public func perform(afterDelay delay: TimeInterval, block: @escaping () -> Void) { tasksLock.lock() defer { tasksLock.unlock() } @@ -123,8 +122,8 @@ import Foundation // Schedules a block to be executed on the main thread after a delay. @objc(performBlock:) - public func perform(block: @escaping () -> Void) { - perform(block: block, afterDelay: 0) + public func perform(_ block: @escaping () -> Void) { + perform(afterDelay: 0, block: block) } private func performExpiredTasks() { diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm index 4e4ebc44967..01fc1eafe87 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm @@ -78,17 +78,17 @@ static const CFTimeInterval kTimerLatencyCompensation = 0.001; TRACE_VSYNC("DisplayLinkCallback-Original", _pendingBaton.value_or(0)); [FlutterRunLoop.mainRunLoop - performBlock:^{ - if (!_pendingBaton.has_value()) { - TRACE_VSYNC("DisplayLinkPaused", size_t(0)); - _displayLink.paused = YES; - return; - } - TRACE_VSYNC("DisplayLinkCallback-Delayed", _pendingBaton.value_or(0)); - _block(minStart, targetTimestamp, *_pendingBaton); - _pendingBaton = std::nullopt; - } - afterDelay:remaining]; + performAfterDelay:remaining + block:^{ + if (!_pendingBaton.has_value()) { + TRACE_VSYNC("DisplayLinkPaused", size_t(0)); + _displayLink.paused = YES; + return; + } + TRACE_VSYNC("DisplayLinkCallback-Delayed", _pendingBaton.value_or(0)); + _block(minStart, targetTimestamp, *_pendingBaton); + _pendingBaton = std::nullopt; + }]; } } @@ -138,13 +138,12 @@ static const CFTimeInterval kTimerLatencyCompensation = 0.001; delay = std::max(start - now - kTimerLatencyCompensation, 0.0); } - [FlutterRunLoop.mainRunLoop - performBlock:^{ - CFTimeInterval targetTimestamp = start + tick_interval; - TRACE_VSYNC("SynthesizedInitialVSync", baton); - _block(start, targetTimestamp, baton); - } - afterDelay:delay]; + [FlutterRunLoop.mainRunLoop performAfterDelay:delay + block:^{ + CFTimeInterval targetTime = start + tick_interval; + TRACE_VSYNC("SynthesizedInitialVSync", baton); + _block(start, targetTime, baton); + }]; _displayLink.paused = NO; } else { _pendingBaton = baton;