| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h" |
| |
| #import <QuartzCore/QuartzCore.h> |
| |
| #include <mutex> |
| #include <unordered_map> |
| #include <vector> |
| |
| #import "flutter/fml/logging.h" |
| #import "flutter/fml/synchronization/waitable_event.h" |
| |
| @interface FlutterThreadSynchronizer () { |
| dispatch_queue_t _mainQueue; |
| std::mutex _mutex; |
| BOOL _shuttingDown; |
| std::unordered_map<int64_t, CGSize> _contentSizes; |
| std::vector<dispatch_block_t> _scheduledBlocks; |
| |
| BOOL _beginResizeWaiting; |
| |
| // Used to block [beginResize:]. |
| std::condition_variable _condBlockBeginResize; |
| } |
| |
| /** |
| * Returns true if all existing views have a non-zero size. |
| * |
| * If there are no views, still returns true. |
| */ |
| - (BOOL)allViewsHaveFrame; |
| |
| /** |
| * Returns true if there are any views that have a non-zero size. |
| * |
| * If there are no views, returns false. |
| */ |
| - (BOOL)someViewsHaveFrame; |
| |
| @end |
| |
| @implementation FlutterThreadSynchronizer |
| |
| - (instancetype)init { |
| return [self initWithMainQueue:dispatch_get_main_queue()]; |
| } |
| |
| - (instancetype)initWithMainQueue:(dispatch_queue_t)queue { |
| self = [super init]; |
| if (self != nil) { |
| _mainQueue = queue; |
| } |
| return self; |
| } |
| |
| - (BOOL)allViewsHaveFrame { |
| for (auto const& [viewId, contentSize] : _contentSizes) { |
| if (CGSizeEqualToSize(contentSize, CGSizeZero)) { |
| return NO; |
| } |
| } |
| return YES; |
| } |
| |
| - (BOOL)someViewsHaveFrame { |
| for (auto const& [viewId, contentSize] : _contentSizes) { |
| if (!CGSizeEqualToSize(contentSize, CGSizeZero)) { |
| return YES; |
| } |
| } |
| return NO; |
| } |
| |
| - (void)drain { |
| dispatch_assert_queue(_mainQueue); |
| |
| [CATransaction begin]; |
| [CATransaction setDisableActions:YES]; |
| for (dispatch_block_t block : _scheduledBlocks) { |
| block(); |
| } |
| [CATransaction commit]; |
| _scheduledBlocks.clear(); |
| } |
| |
| - (void)blockUntilFrameAvailable { |
| std::unique_lock<std::mutex> lock(_mutex); |
| |
| _beginResizeWaiting = YES; |
| |
| while (![self someViewsHaveFrame] && !_shuttingDown) { |
| _condBlockBeginResize.wait(lock); |
| [self drain]; |
| } |
| |
| _beginResizeWaiting = NO; |
| } |
| |
| - (void)beginResizeForView:(int64_t)viewId |
| size:(CGSize)size |
| notify:(nonnull dispatch_block_t)notify { |
| dispatch_assert_queue(_mainQueue); |
| std::unique_lock<std::mutex> lock(_mutex); |
| |
| if (![self allViewsHaveFrame] || _shuttingDown) { |
| // No blocking until framework produces at least one frame |
| notify(); |
| return; |
| } |
| |
| [self drain]; |
| |
| notify(); |
| |
| _contentSizes[viewId] = CGSizeMake(-1, -1); |
| |
| _beginResizeWaiting = YES; |
| |
| while (true) { |
| if (_shuttingDown) { |
| break; |
| } |
| const CGSize& contentSize = _contentSizes[viewId]; |
| if (CGSizeEqualToSize(contentSize, size) || CGSizeEqualToSize(contentSize, CGSizeZero)) { |
| break; |
| } |
| _condBlockBeginResize.wait(lock); |
| [self drain]; |
| } |
| |
| _beginResizeWaiting = NO; |
| } |
| |
| - (void)performCommitForView:(int64_t)viewId |
| size:(CGSize)size |
| notify:(nonnull dispatch_block_t)notify { |
| dispatch_assert_queue_not(_mainQueue); |
| fml::AutoResetWaitableEvent event; |
| { |
| std::unique_lock<std::mutex> lock(_mutex); |
| if (_shuttingDown) { |
| // Engine is shutting down, main thread may be blocked by the engine |
| // waiting for raster thread to finish. |
| return; |
| } |
| fml::AutoResetWaitableEvent& e = event; |
| _scheduledBlocks.push_back(^{ |
| notify(); |
| _contentSizes[viewId] = size; |
| e.Signal(); |
| }); |
| if (_beginResizeWaiting) { |
| _condBlockBeginResize.notify_all(); |
| } else { |
| dispatch_async(_mainQueue, ^{ |
| std::unique_lock<std::mutex> lock(_mutex); |
| [self drain]; |
| }); |
| } |
| } |
| event.Wait(); |
| } |
| |
| - (void)registerView:(int64_t)viewId { |
| dispatch_assert_queue(_mainQueue); |
| std::unique_lock<std::mutex> lock(_mutex); |
| _contentSizes[viewId] = CGSizeZero; |
| } |
| |
| - (void)deregisterView:(int64_t)viewId { |
| dispatch_assert_queue(_mainQueue); |
| std::unique_lock<std::mutex> lock(_mutex); |
| _contentSizes.erase(viewId); |
| } |
| |
| - (void)shutdown { |
| dispatch_assert_queue(_mainQueue); |
| std::unique_lock<std::mutex> lock(_mutex); |
| _shuttingDown = YES; |
| _condBlockBeginResize.notify_all(); |
| [self drain]; |
| } |
| |
| - (BOOL)isWaitingWhenMutexIsAvailable { |
| std::unique_lock<std::mutex> lock(_mutex); |
| return _beginResizeWaiting; |
| } |
| |
| @end |