| // 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 <algorithm> |
| #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/FlutterGLCompositor.h" |
| #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h" |
| #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.h" |
| #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderingBackend.h" |
| #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" |
| #import "flutter/shell/platform/embedder/embedder.h" |
| |
| /** |
| * Constructs and returns a FlutterLocale struct corresponding to |locale|, which must outlive |
| * the returned struct. |
| */ |
| static FlutterLocale FlutterLocaleFromNSLocale(NSLocale* locale) { |
| FlutterLocale flutterLocale = {}; |
| flutterLocale.struct_size = sizeof(FlutterLocale); |
| flutterLocale.language_code = [[locale objectForKey:NSLocaleLanguageCode] UTF8String]; |
| flutterLocale.country_code = [[locale objectForKey:NSLocaleCountryCode] UTF8String]; |
| flutterLocale.script_code = [[locale objectForKey:NSLocaleScriptCode] UTF8String]; |
| flutterLocale.variant_code = [[locale objectForKey:NSLocaleVariantCode] UTF8String]; |
| return flutterLocale; |
| } |
| |
| /** |
| * Private interface declaration for FlutterEngine. |
| */ |
| @interface FlutterEngine () <FlutterBinaryMessenger> |
| |
| /** |
| * Sends the list of user-preferred locales to the Flutter engine. |
| */ |
| - (void)sendUserLocales; |
| |
| /** |
| * Handles a platform message from the engine. |
| */ |
| - (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message; |
| |
| /** |
| * 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; |
| |
| /** |
| * Loads the AOT snapshots and instructions from the elf bundle (app_elf_snapshot.so) into _aotData, |
| * if it is present in the assets directory. |
| */ |
| - (void)loadAOTData:(NSString*)assetsDir; |
| |
| @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.renderer; |
| } |
| |
| - (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 void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngine* engine) { |
| [engine engineCallbackOnPlatformMessage:message]; |
| } |
| |
| #pragma mark - |
| |
| @implementation FlutterEngine { |
| // The embedding-API-level engine object. |
| FLUTTER_API_SYMBOL(FlutterEngine) _engine; |
| |
| // The project being run by this engine. |
| FlutterDartProject* _project; |
| |
| // 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; |
| |
| // Pointer to the Dart AOT snapshot and instruction data. |
| _FlutterEngineAOTData* _aotData; |
| |
| // _macOSGLCompositor is created when the engine is created and |
| // it's destruction is handled by ARC when the engine is destroyed. |
| std::unique_ptr<flutter::FlutterGLCompositor> _macOSGLCompositor; |
| |
| // FlutterCompositor is copied and used in embedder.cc. |
| FlutterCompositor _compositor; |
| } |
| |
| - (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]; |
| _allowHeadlessExecution = allowHeadlessExecution; |
| |
| _embedderAPI.struct_size = sizeof(FlutterEngineProcTable); |
| FlutterEngineGetProcAddresses(&_embedderAPI); |
| |
| if ([FlutterRenderingBackend renderUsingMetal]) { |
| _renderer = [[FlutterMetalRenderer alloc] initWithFlutterEngine:self]; |
| } else { |
| _renderer = [[FlutterOpenGLRenderer alloc] initWithFlutterEngine:self]; |
| } |
| |
| NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; |
| [notificationCenter addObserver:self |
| selector:@selector(sendUserLocales) |
| name:NSCurrentLocaleDidChangeNotification |
| object:nil]; |
| |
| return self; |
| } |
| |
| - (void)dealloc { |
| [self shutDownEngine]; |
| if (_aotData) { |
| _embedderAPI.CollectAOTData(_aotData); |
| } |
| } |
| |
| - (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; |
| } |
| |
| // TODO(stuartmorgan): Move internal channel registration from FlutterViewController to here. |
| |
| // FlutterProjectArgs is expecting a full argv, so when processing it for |
| // flags the first item is treated as the executable and ignored. Add a dummy |
| // value so that all provided arguments are used. |
| std::vector<std::string> switches = _project.switches; |
| std::vector<const char*> argv = {"placeholder"}; |
| std::transform(switches.begin(), switches.end(), std::back_inserter(argv), |
| [](const std::string& arg) -> const char* { return arg.c_str(); }); |
| |
| std::vector<const char*> dartEntrypointArgs; |
| for (NSString* argument in [_project dartEntrypointArguments]) { |
| dartEntrypointArgs.push_back([argument UTF8String]); |
| } |
| |
| FlutterProjectArgs flutterArguments = {}; |
| flutterArguments.struct_size = sizeof(FlutterProjectArgs); |
| flutterArguments.assets_path = _project.assetsPath.UTF8String; |
| flutterArguments.icu_data_path = _project.ICUDataPath.UTF8String; |
| flutterArguments.command_line_argc = static_cast<int>(argv.size()); |
| flutterArguments.command_line_argv = argv.size() > 0 ? argv.data() : nullptr; |
| flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage; |
| flutterArguments.custom_dart_entrypoint = entrypoint.UTF8String; |
| flutterArguments.shutdown_dart_vm_when_done = true; |
| flutterArguments.dart_entrypoint_argc = dartEntrypointArgs.size(); |
| flutterArguments.dart_entrypoint_argv = dartEntrypointArgs.data(); |
| |
| 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, |
| }; |
| flutterArguments.custom_task_runners = &custom_task_runners; |
| |
| [self loadAOTData:_project.assetsPath]; |
| if (_aotData) { |
| flutterArguments.aot_data = _aotData; |
| } |
| |
| flutterArguments.compositor = [self createFlutterCompositor]; |
| |
| FlutterRendererConfig rendererConfig = [_renderer createRendererConfig]; |
| FlutterEngineResult result = _embedderAPI.Initialize( |
| FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); |
| if (result != kSuccess) { |
| NSLog(@"Failed to initialize Flutter engine: error %d", result); |
| return NO; |
| } |
| |
| result = _embedderAPI.RunInitialized(_engine); |
| if (result != kSuccess) { |
| NSLog(@"Failed to run an initialized engine: error %d", result); |
| return NO; |
| } |
| |
| [self sendUserLocales]; |
| [self updateWindowMetrics]; |
| [self updateDisplayConfig]; |
| return YES; |
| } |
| |
| - (void)loadAOTData:(NSString*)assetsDir { |
| if (!_embedderAPI.RunsAOTCompiledDartCode()) { |
| return; |
| } |
| |
| BOOL isDirOut = false; // required for NSFileManager fileExistsAtPath. |
| NSFileManager* fileManager = [NSFileManager defaultManager]; |
| |
| // This is the location where the test fixture places the snapshot file. |
| // For applications built by Flutter tool, this is in "App.framework". |
| NSString* elfPath = [NSString pathWithComponents:@[ assetsDir, @"app_elf_snapshot.so" ]]; |
| |
| if (![fileManager fileExistsAtPath:elfPath isDirectory:&isDirOut]) { |
| return; |
| } |
| |
| FlutterEngineAOTDataSource source = {}; |
| source.type = kFlutterEngineAOTDataSourceTypeElfPath; |
| source.elf_path = [elfPath cStringUsingEncoding:NSUTF8StringEncoding]; |
| |
| auto result = _embedderAPI.CreateAOTData(&source, &_aotData); |
| if (result != kSuccess) { |
| NSLog(@"Failed to load AOT data from: %@", elfPath); |
| } |
| } |
| |
| - (void)setViewController:(FlutterViewController*)controller { |
| _viewController = controller; |
| [_renderer setFlutterView:controller.flutterView]; |
| |
| if (!controller && !_allowHeadlessExecution) { |
| [self shutDownEngine]; |
| } |
| } |
| |
| - (FlutterCompositor*)createFlutterCompositor { |
| // When rendering with metal do not support platform views. |
| if ([FlutterRenderingBackend renderUsingMetal]) { |
| return nil; |
| } |
| |
| // TODO(richardjcai): Add support for creating a FlutterGLCompositor |
| // with a nil _viewController for headless engines. |
| // https://github.com/flutter/flutter/issues/71606 |
| if (!_viewController) { |
| return nil; |
| } |
| |
| FlutterOpenGLRenderer* openGLRenderer = reinterpret_cast<FlutterOpenGLRenderer*>(_renderer); |
| [openGLRenderer.openGLContext makeCurrentContext]; |
| |
| _macOSGLCompositor = |
| std::make_unique<flutter::FlutterGLCompositor>(_viewController, openGLRenderer.openGLContext); |
| |
| _compositor = {}; |
| _compositor.struct_size = sizeof(FlutterCompositor); |
| _compositor.user_data = _macOSGLCompositor.get(); |
| |
| _compositor.create_backing_store_callback = [](const FlutterBackingStoreConfig* config, // |
| FlutterBackingStore* backing_store_out, // |
| void* user_data // |
| ) { |
| return reinterpret_cast<flutter::FlutterGLCompositor*>(user_data)->CreateBackingStore( |
| config, backing_store_out); |
| }; |
| |
| _compositor.collect_backing_store_callback = [](const FlutterBackingStore* backing_store, // |
| void* user_data // |
| ) { |
| return reinterpret_cast<flutter::FlutterGLCompositor*>(user_data)->CollectBackingStore( |
| backing_store); |
| }; |
| |
| _compositor.present_layers_callback = [](const FlutterLayer** layers, // |
| size_t layers_count, // |
| void* user_data // |
| ) { |
| return reinterpret_cast<flutter::FlutterGLCompositor*>(user_data)->Present(layers, |
| layers_count); |
| }; |
| |
| __weak FlutterEngine* weakSelf = self; |
| _macOSGLCompositor->SetPresentCallback([weakSelf]() { |
| FlutterOpenGLRenderer* openGLRenderer = |
| reinterpret_cast<FlutterOpenGLRenderer*>(weakSelf.renderer); |
| return [openGLRenderer glPresent]; |
| }); |
| |
| _compositor.avoid_backing_store_cache = true; |
| |
| return &_compositor; |
| } |
| |
| - (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; |
| } |
| |
| - (void)updateDisplayConfig { |
| if (!_engine) { |
| return; |
| } |
| |
| CVDisplayLinkRef displayLinkRef; |
| CGDirectDisplayID mainDisplayID = CGMainDisplayID(); |
| CVDisplayLinkCreateWithCGDisplay(mainDisplayID, &displayLinkRef); |
| CVTime nominal = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLinkRef); |
| if (!(nominal.flags & kCVTimeIsIndefinite)) { |
| double refreshRate = static_cast<double>(nominal.timeScale) / nominal.timeValue; |
| |
| FlutterEngineDisplay display; |
| display.struct_size = sizeof(display); |
| display.display_id = mainDisplayID; |
| display.refresh_rate = round(refreshRate); |
| |
| std::vector<FlutterEngineDisplay> displays = {display}; |
| _embedderAPI.NotifyDisplayUpdate(_engine, kFlutterEngineDisplaysUpdateTypeStartup, |
| displays.data(), displays.size()); |
| } |
| |
| CVDisplayLinkRelease(displayLinkRef); |
| } |
| |
| - (FlutterEngineProcTable&)embedderAPI { |
| return _embedderAPI; |
| } |
| |
| - (void)updateWindowMetrics { |
| if (!_engine) { |
| return; |
| } |
| NSView* view = _viewController.view; |
| CGRect scaledBounds = [view convertRectToBacking:view.bounds]; |
| CGSize scaledSize = scaledBounds.size; |
| double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width; |
| |
| const FlutterWindowMetricsEvent windowMetricsEvent = { |
| .struct_size = sizeof(windowMetricsEvent), |
| .width = static_cast<size_t>(scaledSize.width), |
| .height = static_cast<size_t>(scaledSize.height), |
| .pixel_ratio = pixelRatio, |
| .left = static_cast<size_t>(scaledBounds.origin.x), |
| .top = static_cast<size_t>(scaledBounds.origin.y), |
| }; |
| _embedderAPI.SendWindowMetricsEvent(_engine, &windowMetricsEvent); |
| } |
| |
| - (void)sendPointerEvent:(const FlutterPointerEvent&)event { |
| _embedderAPI.SendPointerEvent(_engine, &event, 1); |
| } |
| |
| #pragma mark - Private methods |
| |
| - (void)sendUserLocales { |
| if (!self.running) { |
| return; |
| } |
| |
| // Create a list of FlutterLocales corresponding to the preferred languages. |
| NSMutableArray<NSLocale*>* locales = [NSMutableArray array]; |
| std::vector<FlutterLocale> flutterLocales; |
| flutterLocales.reserve(locales.count); |
| for (NSString* localeID in [NSLocale preferredLanguages]) { |
| NSLocale* locale = [[NSLocale alloc] initWithLocaleIdentifier:localeID]; |
| [locales addObject:locale]; |
| flutterLocales.push_back(FlutterLocaleFromNSLocale(locale)); |
| } |
| // Convert to a list of pointers, and send to the engine. |
| std::vector<const FlutterLocale*> flutterLocaleList; |
| flutterLocaleList.reserve(flutterLocales.size()); |
| std::transform( |
| flutterLocales.begin(), flutterLocales.end(), std::back_inserter(flutterLocaleList), |
| [](const auto& arg) -> const auto* { return &arg; }); |
| _embedderAPI.UpdateLocales(_engine, flutterLocaleList.data(), flutterLocaleList.size()); |
| } |
| |
| - (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) { |
| _embedderAPI.SendPlatformMessageResponse(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; |
| } |
| |
| if (_viewController && _viewController.flutterView) { |
| [_viewController.flutterView shutdown]; |
| } |
| |
| FlutterEngineResult result = _embedderAPI.Deinitialize(_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 = _embedderAPI.Shutdown(_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 = _embedderAPI.PlatformMessageCreateResponseHandle( |
| _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 = _embedderAPI.SendPlatformMessage(_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 = |
| _embedderAPI.PlatformMessageReleaseResponseHandle(_engine, response_handle); |
| if (release_result != kSuccess) { |
| NSLog(@"Failed to release the response handle (%d).", release_result); |
| }; |
| } |
| } |
| |
| - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString*)channel |
| binaryMessageHandler: |
| (nullable FlutterBinaryMessageHandler)handler { |
| _messageHandlers[channel] = [handler copy]; |
| return 0; |
| } |
| |
| - (void)cleanupConnection:(FlutterBinaryMessengerConnection)connection { |
| // There hasn't been a need to implement this yet for macOS. |
| } |
| |
| #pragma mark - FlutterPluginRegistry |
| |
| - (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName { |
| return [[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self]; |
| } |
| |
| #pragma mark - FlutterTextureRegistrar |
| |
| - (int64_t)registerTexture:(id<FlutterTexture>)texture { |
| return [_renderer registerTexture:texture]; |
| } |
| |
| - (BOOL)registerTextureWithID:(int64_t)textureId { |
| return _embedderAPI.RegisterExternalTexture(_engine, textureId) == kSuccess; |
| } |
| |
| - (void)textureFrameAvailable:(int64_t)textureID { |
| [_renderer textureFrameAvailable:textureID]; |
| } |
| |
| - (BOOL)markTextureFrameAvailable:(int64_t)textureID { |
| return _embedderAPI.MarkExternalTextureFrameAvailable(_engine, textureID) == kSuccess; |
| } |
| |
| - (void)unregisterTexture:(int64_t)textureID { |
| [_renderer unregisterTexture:textureID]; |
| } |
| |
| - (BOOL)unregisterTextureWithID:(int64_t)textureID { |
| return _embedderAPI.UnregisterExternalTexture(_engine, textureID) == kSuccess; |
| } |
| |
| #pragma mark - Task runner integration |
| |
| - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime { |
| const auto engine_time = _embedderAPI.GetCurrentTime(); |
| |
| __weak FlutterEngine* weak_self = self; |
| auto worker = ^{ |
| FlutterEngine* strong_self = weak_self; |
| if (strong_self && strong_self->_engine) { |
| auto result = _embedderAPI.RunTask(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 |