blob: 75a4ff5037210be93a02777fe794de29f94e28c1 [file] [log] [blame]
// 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