mirror of
https://github.com/encounter/engine.git
synced 2026-07-10 03:18:43 -07:00
e3c5f4827d
Also creates a new test harness for the desktop embedder framework target and adds a test that launches a headless engine in this new thread configuration. Fixes https://github.com/flutter/flutter/issues/17579
512 lines
17 KiB
Plaintext
512 lines
17 KiB
Plaintext
// 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/Headers/FlutterEngine.h"
|
|
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
|
|
|
#include <vector>
|
|
|
|
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
|
|
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h"
|
|
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
|
|
#import "flutter/shell/platform/embedder/embedder.h"
|
|
|
|
/**
|
|
* Private interface declaration for FlutterEngine.
|
|
*/
|
|
@interface FlutterEngine () <FlutterBinaryMessenger>
|
|
|
|
/**
|
|
* Called by the engine to make the context the engine should draw into current.
|
|
*/
|
|
- (bool)engineCallbackOnMakeCurrent;
|
|
|
|
/**
|
|
* Called by the engine to clear the context the engine should draw into.
|
|
*/
|
|
- (bool)engineCallbackOnClearCurrent;
|
|
|
|
/**
|
|
* Called by the engine when the context's buffers should be swapped.
|
|
*/
|
|
- (bool)engineCallbackOnPresent;
|
|
|
|
/**
|
|
* Makes the resource context the current context.
|
|
*/
|
|
- (bool)engineCallbackOnMakeResourceCurrent;
|
|
|
|
/**
|
|
* Handles a platform message from the engine.
|
|
*/
|
|
- (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message;
|
|
|
|
/**
|
|
* Forwards texture copy request to the corresponding texture via |textureID|.
|
|
*/
|
|
- (BOOL)populateTextureWithIdentifier:(int64_t)textureID
|
|
openGLTexture:(FlutterOpenGLTexture*)openGLTexture;
|
|
|
|
/**
|
|
* Requests that the task be posted back the to the Flutter engine at the target time. The target
|
|
* time is in the clock used by the Flutter engine.
|
|
*/
|
|
- (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime;
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
/**
|
|
* `FlutterPluginRegistrar` implementation handling a single plugin.
|
|
*/
|
|
@interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
|
|
- (instancetype)initWithPlugin:(nonnull NSString*)pluginKey
|
|
flutterEngine:(nonnull FlutterEngine*)flutterEngine;
|
|
@end
|
|
|
|
@implementation FlutterEngineRegistrar {
|
|
NSString* _pluginKey;
|
|
FlutterEngine* _flutterEngine;
|
|
}
|
|
|
|
- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine {
|
|
self = [super init];
|
|
if (self) {
|
|
_pluginKey = [pluginKey copy];
|
|
_flutterEngine = flutterEngine;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - FlutterPluginRegistrar
|
|
|
|
- (id<FlutterBinaryMessenger>)messenger {
|
|
return _flutterEngine.binaryMessenger;
|
|
}
|
|
|
|
- (id<FlutterTextureRegistry>)textures {
|
|
return _flutterEngine;
|
|
}
|
|
|
|
- (NSView*)view {
|
|
return _flutterEngine.viewController.view;
|
|
}
|
|
|
|
- (void)addMethodCallDelegate:(nonnull id<FlutterPlugin>)delegate
|
|
channel:(nonnull FlutterMethodChannel*)channel {
|
|
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
|
|
[delegate handleMethodCall:call result:result];
|
|
}];
|
|
}
|
|
|
|
@end
|
|
|
|
// Callbacks provided to the engine. See the called methods for documentation.
|
|
#pragma mark - Static methods provided to engine configuration
|
|
|
|
static bool OnMakeCurrent(FlutterEngine* engine) {
|
|
return [engine engineCallbackOnMakeCurrent];
|
|
}
|
|
|
|
static bool OnClearCurrent(FlutterEngine* engine) {
|
|
return [engine engineCallbackOnClearCurrent];
|
|
}
|
|
|
|
static bool OnPresent(FlutterEngine* engine) {
|
|
return [engine engineCallbackOnPresent];
|
|
}
|
|
|
|
static uint32_t OnFBO(FlutterEngine* engine) {
|
|
// There is currently no case where a different FBO is used, so no need to forward.
|
|
return 0;
|
|
}
|
|
|
|
static bool OnMakeResourceCurrent(FlutterEngine* engine) {
|
|
return [engine engineCallbackOnMakeResourceCurrent];
|
|
}
|
|
|
|
static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngine* engine) {
|
|
[engine engineCallbackOnPlatformMessage:message];
|
|
}
|
|
|
|
static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
|
int64_t texture_identifier,
|
|
size_t width,
|
|
size_t height,
|
|
FlutterOpenGLTexture* open_gl_texture) {
|
|
return [engine populateTextureWithIdentifier:texture_identifier openGLTexture:open_gl_texture];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
@implementation FlutterEngine {
|
|
// The embedding-API-level engine object.
|
|
FLUTTER_API_SYMBOL(FlutterEngine) _engine;
|
|
|
|
// The project being run by this engine.
|
|
FlutterDartProject* _project;
|
|
|
|
// The context provided to the Flutter engine for resource loading.
|
|
NSOpenGLContext* _resourceContext;
|
|
|
|
// The context that is owned by the currently displayed FlutterView. This is stashed in the engine
|
|
// so that the view doesn't need to be accessed from a background thread.
|
|
NSOpenGLContext* _mainOpenGLContext;
|
|
|
|
// A mapping of channel names to the registered handlers for those channels.
|
|
NSMutableDictionary<NSString*, FlutterBinaryMessageHandler>* _messageHandlers;
|
|
|
|
// Whether the engine can continue running after the view controller is removed.
|
|
BOOL _allowHeadlessExecution;
|
|
|
|
// A mapping of textureID to internal FlutterExternalTextureGL adapter.
|
|
NSMutableDictionary<NSNumber*, FlutterExternalTextureGL*>* _textures;
|
|
}
|
|
|
|
- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
|
|
return [self initWithName:labelPrefix project:project allowHeadlessExecution:YES];
|
|
}
|
|
|
|
- (instancetype)initWithName:(NSString*)labelPrefix
|
|
project:(FlutterDartProject*)project
|
|
allowHeadlessExecution:(BOOL)allowHeadlessExecution {
|
|
self = [super init];
|
|
NSAssert(self, @"Super init cannot be nil");
|
|
|
|
_project = project ?: [[FlutterDartProject alloc] init];
|
|
_messageHandlers = [[NSMutableDictionary alloc] init];
|
|
_textures = [[NSMutableDictionary alloc] init];
|
|
_allowHeadlessExecution = allowHeadlessExecution;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self shutDownEngine];
|
|
}
|
|
|
|
- (BOOL)runWithEntrypoint:(NSString*)entrypoint {
|
|
if (self.running) {
|
|
return NO;
|
|
}
|
|
|
|
if (!_allowHeadlessExecution && !_viewController) {
|
|
NSLog(@"Attempted to run an engine with no view controller without headless mode enabled.");
|
|
return NO;
|
|
}
|
|
|
|
const FlutterRendererConfig rendererConfig = {
|
|
.type = kOpenGL,
|
|
.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig),
|
|
.open_gl.make_current = (BoolCallback)OnMakeCurrent,
|
|
.open_gl.clear_current = (BoolCallback)OnClearCurrent,
|
|
.open_gl.present = (BoolCallback)OnPresent,
|
|
.open_gl.fbo_callback = (UIntCallback)OnFBO,
|
|
.open_gl.make_resource_current = (BoolCallback)OnMakeResourceCurrent,
|
|
.open_gl.gl_external_texture_frame_callback = (TextureFrameCallback)OnAcquireExternalTexture,
|
|
};
|
|
|
|
// TODO(stuartmorgan): Move internal channel registration from FlutterViewController to here.
|
|
|
|
FlutterProjectArgs flutterArguments = {};
|
|
flutterArguments.struct_size = sizeof(FlutterProjectArgs);
|
|
flutterArguments.assets_path = _project.assetsPath.UTF8String;
|
|
flutterArguments.icu_data_path = _project.ICUDataPath.UTF8String;
|
|
std::vector<const char*> arguments = _project.argv;
|
|
flutterArguments.command_line_argc = static_cast<int>(arguments.size());
|
|
flutterArguments.command_line_argv = &arguments[0];
|
|
flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage;
|
|
flutterArguments.custom_dart_entrypoint = entrypoint.UTF8String;
|
|
static size_t sTaskRunnerIdentifiers = 0;
|
|
const FlutterTaskRunnerDescription cocoa_task_runner_description = {
|
|
.struct_size = sizeof(FlutterTaskRunnerDescription),
|
|
.user_data = (void*)CFBridgingRetain(self),
|
|
.runs_task_on_current_thread_callback = [](void* user_data) -> bool {
|
|
return [[NSThread currentThread] isMainThread];
|
|
},
|
|
.post_task_callback = [](FlutterTask task, uint64_t target_time_nanos,
|
|
void* user_data) -> void {
|
|
[((__bridge FlutterEngine*)(user_data)) postMainThreadTask:task
|
|
targetTimeInNanoseconds:target_time_nanos];
|
|
},
|
|
.identifier = ++sTaskRunnerIdentifiers,
|
|
};
|
|
const FlutterCustomTaskRunners custom_task_runners = {
|
|
.struct_size = sizeof(FlutterCustomTaskRunners),
|
|
.platform_task_runner = &cocoa_task_runner_description,
|
|
.render_task_runner = &cocoa_task_runner_description,
|
|
};
|
|
flutterArguments.custom_task_runners = &custom_task_runners;
|
|
|
|
FlutterEngineResult result = FlutterEngineInitialize(
|
|
FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine);
|
|
if (result != kSuccess) {
|
|
NSLog(@"Failed to initialize Flutter engine: error %d", result);
|
|
return NO;
|
|
}
|
|
|
|
result = FlutterEngineRunInitialized(_engine);
|
|
if (result != kSuccess) {
|
|
NSLog(@"Failed to run an initialized engine: error %d", result);
|
|
return NO;
|
|
}
|
|
|
|
[self updateWindowMetrics];
|
|
return YES;
|
|
}
|
|
|
|
- (void)setViewController:(FlutterViewController*)controller {
|
|
_viewController = controller;
|
|
_mainOpenGLContext = controller.flutterView.openGLContext;
|
|
if (!controller && !_allowHeadlessExecution) {
|
|
[self shutDownEngine];
|
|
_resourceContext = nil;
|
|
}
|
|
[self updateWindowMetrics];
|
|
}
|
|
|
|
- (id<FlutterBinaryMessenger>)binaryMessenger {
|
|
// TODO(stuartmorgan): Switch to FlutterBinaryMessengerRelay to avoid plugins
|
|
// keeping the engine alive.
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - Framework-internal methods
|
|
|
|
- (BOOL)running {
|
|
return _engine != nullptr;
|
|
}
|
|
|
|
- (NSOpenGLContext*)resourceContext {
|
|
if (!_resourceContext) {
|
|
NSOpenGLPixelFormatAttribute attributes[] = {
|
|
NSOpenGLPFAColorSize, 24, NSOpenGLPFAAlphaSize, 8, NSOpenGLPFADoubleBuffer, 0,
|
|
};
|
|
NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
|
|
_resourceContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
|
|
}
|
|
return _resourceContext;
|
|
}
|
|
|
|
- (void)updateWindowMetrics {
|
|
if (!_engine) {
|
|
return;
|
|
}
|
|
NSView* view = _viewController.view;
|
|
CGSize scaledSize = [view convertRectToBacking:view.bounds].size;
|
|
double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width;
|
|
|
|
const FlutterWindowMetricsEvent event = {
|
|
.struct_size = sizeof(event),
|
|
.width = static_cast<size_t>(scaledSize.width),
|
|
.height = static_cast<size_t>(scaledSize.height),
|
|
.pixel_ratio = pixelRatio,
|
|
};
|
|
FlutterEngineSendWindowMetricsEvent(_engine, &event);
|
|
}
|
|
|
|
- (void)sendPointerEvent:(const FlutterPointerEvent&)event {
|
|
FlutterEngineSendPointerEvent(_engine, &event, 1);
|
|
}
|
|
|
|
#pragma mark - Private methods
|
|
|
|
- (bool)engineCallbackOnMakeCurrent {
|
|
if (!_mainOpenGLContext) {
|
|
return false;
|
|
}
|
|
[_mainOpenGLContext makeCurrentContext];
|
|
return true;
|
|
}
|
|
|
|
- (bool)engineCallbackOnClearCurrent {
|
|
[NSOpenGLContext clearCurrentContext];
|
|
return true;
|
|
}
|
|
|
|
- (bool)engineCallbackOnPresent {
|
|
if (!_mainOpenGLContext) {
|
|
}
|
|
[_mainOpenGLContext flushBuffer];
|
|
return true;
|
|
}
|
|
|
|
- (bool)engineCallbackOnMakeResourceCurrent {
|
|
[self.resourceContext makeCurrentContext];
|
|
return true;
|
|
}
|
|
|
|
- (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message {
|
|
NSData* messageData = [NSData dataWithBytesNoCopy:(void*)message->message
|
|
length:message->message_size
|
|
freeWhenDone:NO];
|
|
NSString* channel = @(message->channel);
|
|
__block const FlutterPlatformMessageResponseHandle* responseHandle = message->response_handle;
|
|
|
|
FlutterBinaryReply binaryResponseHandler = ^(NSData* response) {
|
|
if (responseHandle) {
|
|
FlutterEngineSendPlatformMessageResponse(self->_engine, responseHandle,
|
|
static_cast<const uint8_t*>(response.bytes),
|
|
response.length);
|
|
responseHandle = NULL;
|
|
} else {
|
|
NSLog(@"Error: Message responses can be sent only once. Ignoring duplicate response "
|
|
"on channel '%@'.",
|
|
channel);
|
|
}
|
|
};
|
|
|
|
FlutterBinaryMessageHandler channelHandler = _messageHandlers[channel];
|
|
if (channelHandler) {
|
|
channelHandler(messageData, binaryResponseHandler);
|
|
} else {
|
|
binaryResponseHandler(nil);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Note: Called from dealloc. Should not use accessors or other methods.
|
|
*/
|
|
- (void)shutDownEngine {
|
|
if (_engine == nullptr) {
|
|
return;
|
|
}
|
|
|
|
FlutterEngineResult result = FlutterEngineDeinitialize(_engine);
|
|
if (result != kSuccess) {
|
|
NSLog(@"Could not de-initialize the Flutter engine: error %d", result);
|
|
}
|
|
|
|
// Balancing release for the retain in the task runner dispatch table.
|
|
CFRelease((CFTypeRef)self);
|
|
|
|
result = FlutterEngineShutdown(_engine);
|
|
if (result != kSuccess) {
|
|
NSLog(@"Failed to shut down Flutter engine: error %d", result);
|
|
}
|
|
_engine = nullptr;
|
|
}
|
|
|
|
#pragma mark - FlutterBinaryMessenger
|
|
|
|
- (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message {
|
|
[self sendOnChannel:channel message:message binaryReply:nil];
|
|
}
|
|
|
|
- (void)sendOnChannel:(NSString*)channel
|
|
message:(NSData* _Nullable)message
|
|
binaryReply:(FlutterBinaryReply _Nullable)callback {
|
|
FlutterPlatformMessageResponseHandle* response_handle = nullptr;
|
|
if (callback) {
|
|
struct Captures {
|
|
FlutterBinaryReply reply;
|
|
};
|
|
auto captures = std::make_unique<Captures>();
|
|
captures->reply = callback;
|
|
auto message_reply = [](const uint8_t* data, size_t data_size, void* user_data) {
|
|
auto captures = reinterpret_cast<Captures*>(user_data);
|
|
NSData* reply_data = nil;
|
|
if (data != nullptr && data_size > 0) {
|
|
reply_data = [NSData dataWithBytes:static_cast<const void*>(data) length:data_size];
|
|
}
|
|
captures->reply(reply_data);
|
|
delete captures;
|
|
};
|
|
|
|
FlutterEngineResult create_result = FlutterPlatformMessageCreateResponseHandle(
|
|
_engine, message_reply, captures.get(), &response_handle);
|
|
if (create_result != kSuccess) {
|
|
NSLog(@"Failed to create a FlutterPlatformMessageResponseHandle (%d)", create_result);
|
|
return;
|
|
}
|
|
captures.release();
|
|
}
|
|
|
|
FlutterPlatformMessage platformMessage = {
|
|
.struct_size = sizeof(FlutterPlatformMessage),
|
|
.channel = [channel UTF8String],
|
|
.message = static_cast<const uint8_t*>(message.bytes),
|
|
.message_size = message.length,
|
|
.response_handle = response_handle,
|
|
};
|
|
|
|
FlutterEngineResult message_result = FlutterEngineSendPlatformMessage(_engine, &platformMessage);
|
|
if (message_result != kSuccess) {
|
|
NSLog(@"Failed to send message to Flutter engine on channel '%@' (%d).", channel,
|
|
message_result);
|
|
}
|
|
|
|
if (response_handle != nullptr) {
|
|
FlutterEngineResult release_result =
|
|
FlutterPlatformMessageReleaseResponseHandle(_engine, response_handle);
|
|
if (release_result != kSuccess) {
|
|
NSLog(@"Failed to release the response handle (%d).", release_result);
|
|
};
|
|
}
|
|
}
|
|
|
|
- (void)setMessageHandlerOnChannel:(nonnull NSString*)channel
|
|
binaryMessageHandler:(nullable FlutterBinaryMessageHandler)handler {
|
|
_messageHandlers[channel] = [handler copy];
|
|
}
|
|
|
|
#pragma mark - FlutterPluginRegistry
|
|
|
|
- (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
|
|
return [[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self];
|
|
}
|
|
|
|
#pragma mark - FlutterTextureRegistrar
|
|
|
|
- (BOOL)populateTextureWithIdentifier:(int64_t)textureID
|
|
openGLTexture:(FlutterOpenGLTexture*)openGLTexture {
|
|
return [_textures[@(textureID)] populateTexture:openGLTexture];
|
|
}
|
|
|
|
- (int64_t)registerTexture:(id<FlutterTexture>)texture {
|
|
FlutterExternalTextureGL* FlutterTexture =
|
|
[[FlutterExternalTextureGL alloc] initWithFlutterTexture:texture];
|
|
int64_t textureID = [FlutterTexture textureID];
|
|
FlutterEngineRegisterExternalTexture(_engine, textureID);
|
|
_textures[@(textureID)] = FlutterTexture;
|
|
return textureID;
|
|
}
|
|
|
|
- (void)textureFrameAvailable:(int64_t)textureID {
|
|
FlutterEngineMarkExternalTextureFrameAvailable(_engine, textureID);
|
|
}
|
|
|
|
- (void)unregisterTexture:(int64_t)textureID {
|
|
FlutterEngineUnregisterExternalTexture(_engine, textureID);
|
|
[_textures removeObjectForKey:@(textureID)];
|
|
}
|
|
|
|
#pragma mark - Task runner integration
|
|
|
|
- (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime {
|
|
const auto engine_time = FlutterEngineGetCurrentTime();
|
|
|
|
__weak FlutterEngine* weak_self = self;
|
|
auto worker = ^{
|
|
FlutterEngine* strong_self = weak_self;
|
|
if (strong_self && strong_self->_engine) {
|
|
auto result = FlutterEngineRunTask(strong_self->_engine, &task);
|
|
if (result != kSuccess) {
|
|
NSLog(@"Could not post a task to the Flutter engine.");
|
|
}
|
|
}
|
|
};
|
|
|
|
if (targetTime <= engine_time) {
|
|
dispatch_async(dispatch_get_main_queue(), worker);
|
|
|
|
} else {
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, targetTime - engine_time),
|
|
dispatch_get_main_queue(), worker);
|
|
}
|
|
}
|
|
|
|
@end
|