diff --git a/shell/platform/darwin/macos/framework/Headers/FLEEngine.h b/shell/platform/darwin/macos/framework/Headers/FLEEngine.h index b9b302fcfc23b..307f085b849b2 100644 --- a/shell/platform/darwin/macos/framework/Headers/FLEEngine.h +++ b/shell/platform/darwin/macos/framework/Headers/FLEEngine.h @@ -26,34 +26,46 @@ FLUTTER_EXPORT /** * Initializes an engine with the given viewController. * - * @param viewController The view controller associated with this engine. If nil, the engine - * will be run headless. + * @param labelPrefix Currently unused; in the future, may be used for labelling threads + * as with the iOS FlutterEngine. * @param project The project configuration. If nil, a default FLEDartProject will be used. */ -- (nonnull instancetype)initWithViewController:(nullable FLEViewController*)viewController - project:(nullable FLEDartProject*)project - NS_DESIGNATED_INITIALIZER; +- (nonnull instancetype)initWithName:(nonnull NSString*)labelPrefix + project:(nullable FLEDartProject*)project; /** - * Runs `main()` from this engine's project. + * Initializes an engine with the given viewController. * - * @return YES if the engine launched successfully. + * @param labelPrefix Currently unused; in the future, may be used for labelling threads + * as with the iOS FlutterEngine. + * @param project The project configuration. If nil, a default FLEDartProject will be used. */ -- (BOOL)run; +- (nonnull instancetype)initWithName:(nonnull NSString*)labelPrefix + project:(nullable FLEDartProject*)project + allowHeadlessExecution:(BOOL)allowHeadlessExecution NS_DESIGNATED_INITIALIZER; + +- (nonnull instancetype)init NS_UNAVAILABLE; /** - * The `FLEDartProject` associated with this engine. If nil, a default will be used for `run`. + * Runs a Dart program on an Isolate from the main Dart library (i.e. the library that + * contains `main()`). + * + * The first call to this method will create a new Isolate. Subsequent calls will return + * immediately. * - * TODO(stuartmorgan): Remove this once FLEViewController takes the project as an initializer - * argument. Blocked on currently needing to create it from a XIB due to the view issues - * described in https://github.com/google/flutter-desktop-embedding/issues/10. + * @param entrypoint The name of a top-level function from the same Dart + * library that contains the app's main() function. If this is nil, it will + * default to `main()`. If it is not the app's main() function, that function + * must be decorated with `@pragma(vm:entry-point)` to ensure the method is not + * tree-shaken by the Dart compiler. + * @return YES if the call succeeds in creating and running a Flutter Engine instance; NO otherwise. */ -@property(nonatomic, nullable) FLEDartProject* project; +- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint; /** * The `FLEViewController` associated with this engine, if any. */ -@property(nonatomic, nullable, readonly, weak) FLEViewController* viewController; +@property(nonatomic, nullable, weak) FLEViewController* viewController; /** * The `FlutterBinaryMessenger` for communicating with this engine. diff --git a/shell/platform/darwin/macos/framework/Headers/FLEViewController.h b/shell/platform/darwin/macos/framework/Headers/FLEViewController.h index 2328ec5825c57..2142dce31379c 100644 --- a/shell/platform/darwin/macos/framework/Headers/FLEViewController.h +++ b/shell/platform/darwin/macos/framework/Headers/FLEViewController.h @@ -41,12 +41,16 @@ FLUTTER_EXPORT @property(nonatomic) FlutterMouseTrackingMode mouseTrackingMode; /** - * Launches the Flutter engine with the provided project. + * Initializes a controller that will run the given project. * * @param project The project to run in this view controller. If nil, a default `FLEDartProject` * will be used. - * @return YES if the engine launched successfully. */ -- (BOOL)launchEngineWithProject:(nullable FLEDartProject*)project; +- (nonnull instancetype)initWithProject:(nullable FLEDartProject*)project NS_DESIGNATED_INITIALIZER; + +- (nonnull instancetype)initWithNibName:(nullable NSString*)nibNameOrNil + bundle:(nullable NSBundle*)nibBundleOrNil + NS_DESIGNATED_INITIALIZER; +- (nonnull instancetype)initWithCoder:(nonnull NSCoder*)nibNameOrNil NS_DESIGNATED_INITIALIZER; @end diff --git a/shell/platform/darwin/macos/framework/Source/FLEEngine.mm b/shell/platform/darwin/macos/framework/Source/FLEEngine.mm index 289ab47c4449e..9a50499f309ea 100644 --- a/shell/platform/darwin/macos/framework/Source/FLEEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FLEEngine.mm @@ -41,6 +41,11 @@ - (bool)engineCallbackOnMakeResourceCurrent; */ - (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message; +/** + * Shuts the Flutter engine if it is running. + */ +- (void)shutDownEngine; + @end #pragma mark - @@ -120,20 +125,29 @@ @implementation FLEEngine { // The embedding-API-level engine object. FlutterEngine _engine; + // The project being run by this engine. + FLEDartProject* _project; + + // The context provided to the Flutter engine for resource loading. + NSOpenGLContext* _resourceContext; + // A mapping of channel names to the registered handlers for those channels. NSMutableDictionary* _messageHandlers; + + // Whether the engine can continue running after the view controller is removed. + BOOL _allowHeadlessExecution; } -- (instancetype)init { - return [self initWithViewController:nil project:nil]; +- (instancetype)initWithName:(NSString*)labelPrefix project:(FLEDartProject*)project { + return [self initWithName:labelPrefix project:project allowHeadlessExecution:YES]; } -- (instancetype)initWithViewController:(FLEViewController*)viewController - project:(FLEDartProject*)project { +- (instancetype)initWithName:(NSString*)labelPrefix + project:(FLEDartProject*)project + allowHeadlessExecution:(BOOL)allowHeadlessExecution { self = [super init]; NSAssert(self, @"Super init cannot be nil"); - _viewController = viewController; _project = project ?: [[FLEDartProject alloc] init]; _messageHandlers = [[NSMutableDictionary alloc] init]; @@ -141,17 +155,16 @@ - (instancetype)initWithViewController:(FLEViewController*)viewController } - (void)dealloc { - if (FlutterEngineShutdown(_engine) == kSuccess) { - _engine = NULL; - } + [self shutDownEngine]; } -- (void)setProject:(FLEDartProject*)project { - _project = project ?: [[FLEDartProject alloc] init]; -} +- (BOOL)runWithEntrypoint:(NSString*)entrypoint { + if (self.running) { + return NO; + } -- (BOOL)run { - if (_engine != NULL) { + if (!_allowHeadlessExecution && !_viewController) { + NSLog(@"Attempted to run an engine with no view controller without headless mode enabled."); return NO; } @@ -175,6 +188,7 @@ - (BOOL)run { flutterArguments.command_line_argc = static_cast(arguments.size()); flutterArguments.command_line_argv = &arguments[0]; flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage; + flutterArguments.custom_dart_entrypoint = entrypoint.UTF8String; FlutterEngineResult result = FlutterEngineRun( FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); @@ -182,9 +196,19 @@ - (BOOL)run { NSLog(@"Failed to start Flutter engine: error %d", result); return NO; } + [self updateWindowMetrics]; return YES; } +- (void)setViewController:(FLEViewController*)controller { + _viewController = controller; + if (!controller && !_allowHeadlessExecution) { + [self shutDownEngine]; + _resourceContext = nil; + } + [self updateWindowMetrics]; +} + - (id)binaryMessenger { // TODO(stuartmorgan): Switch to FlutterBinaryMessengerRelay to avoid plugins // keeping the engine alive. @@ -193,11 +217,33 @@ - (BOOL)run { #pragma mark - Framework-internal methods -- (void)updateWindowMetricsWithSize:(CGSize)size pixelRatio:(double)pixelRatio { +- (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.width), - .height = static_cast(size.height), + .width = static_cast(scaledSize.width), + .height = static_cast(scaledSize.height), .pixel_ratio = pixelRatio, }; FlutterEngineSendWindowMetricsEvent(_engine, &event); @@ -237,7 +283,7 @@ - (bool)engineCallbackOnMakeResourceCurrent { if (!_viewController.flutterView) { return false; } - [_viewController makeResourceContextCurrent]; + [self.resourceContext makeCurrentContext]; return true; } @@ -269,6 +315,19 @@ - (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message { } } +/** + * Note: Called from dealloc. Should not use accessors or other methods. + */ +- (void)shutDownEngine { + if (_engine) { + FlutterEngineResult 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 { diff --git a/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h index 80112e205c9e0..18c4957d4007d 100644 --- a/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h @@ -4,17 +4,27 @@ #import "flutter/shell/platform/darwin/macos/framework/Headers/FLEEngine.h" +#import + #import "flutter/shell/platform/embedder/embedder.h" @interface FLEEngine () /** - * Informs the engine that the display region's size has changed. - * - * @param size The size of the display, in pixels. - * @param pixelRatio The number of pixels per screen coordinate. + * True if the engine is currently running. + */ +@property(nonatomic, readonly) BOOL running; + +/** + * The resource context used by the engine for texture uploads. FlutterViews associated with this + * engine should be created to share with this context. + */ +@property(nonatomic, readonly, nullable) NSOpenGLContext* resourceContext; + +/** + * Informs the engine that the associated view controller's view size has changed. */ -- (void)updateWindowMetricsWithSize:(CGSize)size pixelRatio:(double)pixelRatio; +- (void)updateWindowMetrics; /** * Dispatches the given pointer event data to engine. diff --git a/shell/platform/darwin/macos/framework/Source/FLEViewController.mm b/shell/platform/darwin/macos/framework/Source/FLEViewController.mm index 91ad156f26c8f..7b58c81dab55b 100644 --- a/shell/platform/darwin/macos/framework/Source/FLEViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FLEViewController.mm @@ -84,6 +84,11 @@ @interface FLEViewController () */ @property(nonatomic) MouseState mouseState; +/** + * Starts running |engine|, including any initial setup. + */ +- (BOOL)launchEngine; + /** * Updates |trackingArea| for the current tracking settings, creating it with * the correct mode if tracking is enabled, or removing it if not. @@ -95,11 +100,6 @@ - (void)configureTrackingArea; */ - (void)addInternalPlugins; -/** - * Creates the OpenGL context used as the resource context by the engine. - */ -- (void)createResourceContext; - /** * Calls dispatchMouseEvent:phase: with a phase determined by self.mouseState. * @@ -149,8 +149,8 @@ - (void)setClipboardData:(NSDictionary*)data; #pragma mark - FLEViewController implementation. @implementation FLEViewController { - // The additional context provided to the Flutter engine for resource loading. - NSOpenGLContext* _resourceContext; + // The project to run in this controller's engine. + FLEDartProject* _project; // The plugin used to handle text input. This is not an FlutterPlugin, so must be owned // separately. @@ -173,29 +173,46 @@ @implementation FLEViewController { * Performs initialization that's common between the different init paths. */ static void CommonInit(FLEViewController* controller) { - controller->_engine = [[FLEEngine alloc] initWithViewController:controller project:nil]; + controller->_engine = [[FLEEngine alloc] initWithName:@"io.flutter" + project:controller->_project + allowHeadlessExecution:NO]; controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init]; controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow; } - (instancetype)initWithCoder:(NSCoder*)coder { self = [super initWithCoder:coder]; - if (self != nil) { - CommonInit(self); - } + NSAssert(self, @"Super init cannot be nil"); + + CommonInit(self); return self; } - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self != nil) { - CommonInit(self); - } + NSAssert(self, @"Super init cannot be nil"); + + CommonInit(self); + return self; +} + +- (instancetype)initWithProject:(nullable FLEDartProject*)project { + self = [super initWithNibName:nil bundle:nil]; + NSAssert(self, @"Super init cannot be nil"); + + _project = project; + CommonInit(self); return self; } - (void)loadView { - FlutterView* flutterView = [[FlutterView alloc] initWithReshapeListener:self]; + NSOpenGLContext* resourceContext = _engine.resourceContext; + if (!resourceContext) { + NSLog(@"Unable to create FlutterView; no resource context available."); + return; + } + FlutterView* flutterView = [[FlutterView alloc] initWithShareContext:resourceContext + reshapeListener:self]; self.view = flutterView; } @@ -203,6 +220,17 @@ - (void)viewDidLoad { [self configureTrackingArea]; } +- (void)viewWillAppear { + [super viewWillAppear]; + if (!_engine.running) { + [self launchEngine]; + } +} + +- (void)dealloc { + _engine.viewController = nil; +} + #pragma mark - Public methods - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode { @@ -213,25 +241,6 @@ - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode { [self configureTrackingArea]; } -- (BOOL)launchEngineWithProject:(nullable FLEDartProject*)project { - // Set up the resource context. This is done here rather than in viewDidLoad as there's no - // guarantee that viewDidLoad will be called before the engine is started, and the context must - // be valid by that point. - [self createResourceContext]; - - // Register internal plugins before starting the engine. - [self addInternalPlugins]; - - _engine.project = project; - if (![_engine run]) { - return NO; - } - // Send the initial user settings such as brightness and text scale factor - // to the engine. - [self sendInitialSettings]; - return YES; -} - #pragma mark - Framework-internal methods - (FlutterView*)flutterView { @@ -246,12 +255,22 @@ - (void)removeKeyResponder:(NSResponder*)responder { [self.additionalKeyResponders removeObject:responder]; } -- (void)makeResourceContextCurrent { - [_resourceContext makeCurrentContext]; -} - #pragma mark - Private methods +- (BOOL)launchEngine { + // Register internal plugins before starting the engine. + [self addInternalPlugins]; + + _engine.viewController = self; + if (![_engine runWithEntrypoint:nil]) { + return NO; + } + // Send the initial user settings such as brightness and text scale factor + // to the engine. + [self sendInitialSettings]; + return YES; +} + - (void)configureTrackingArea { if (_mouseTrackingMode != FlutterMouseTrackingModeNone && self.view) { NSTrackingAreaOptions options = @@ -301,12 +320,6 @@ - (void)addInternalPlugins { }]; } -- (void)createResourceContext { - NSOpenGLContext* viewContext = ((NSOpenGLView*)self.view).openGLContext; - _resourceContext = [[NSOpenGLContext alloc] initWithFormat:viewContext.pixelFormat - shareContext:viewContext]; -} - - (void)dispatchMouseEvent:(nonnull NSEvent*)event { FlutterPointerPhase phase = _mouseState.buttons == 0 ? (_mouseState.flutter_state_is_down ? kUp : kHover) @@ -455,9 +468,7 @@ - (void)setClipboardData:(NSDictionary*)data { * Responds to view reshape by notifying the engine of the change in dimensions. */ - (void)viewDidReshape:(NSView*)view { - CGSize scaledSize = [view convertRectToBacking:view.bounds].size; - double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width; - [_engine updateWindowMetricsWithSize:scaledSize pixelRatio:pixelRatio]; + [_engine updateWindowMetrics]; } #pragma mark - FlutterPluginRegistry diff --git a/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h index cf5804b706468..756758d6f0a6a 100644 --- a/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h @@ -22,10 +22,4 @@ */ - (void)removeKeyResponder:(nonnull NSResponder*)responder; -/** - * Called when the engine wants to make the resource context current. This must be a context - * that is in the same share group as this controller's view. - */ -- (void)makeResourceContextCurrent; - @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterView.h b/shell/platform/darwin/macos/framework/Source/FlutterView.h index 404ce2442f82b..9342e748dc771 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterView.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterView.h @@ -21,11 +21,13 @@ @interface FlutterView : NSOpenGLView - (nullable instancetype)initWithFrame:(NSRect)frame + shareContext:(nonnull NSOpenGLContext*)shareContext reshapeListener:(nonnull id)reshapeListener NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithReshapeListener: - (nonnull id)reshapeListener; +- (nullable instancetype)initWithShareContext:(nonnull NSOpenGLContext*)shareContext + reshapeListener: + (nonnull id)reshapeListener; - (nullable instancetype)initWithFrame:(NSRect)frameRect pixelFormat:(nullable NSOpenGLPixelFormat*)format NS_UNAVAILABLE; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterView.mm b/shell/platform/darwin/macos/framework/Source/FlutterView.mm index cbb93d063ad95..5f3b46d194960 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterView.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterView.mm @@ -8,18 +8,18 @@ @implementation FlutterView { __weak id _reshapeListener; } -- (instancetype)initWithReshapeListener:(id)reshapeListener { - return [self initWithFrame:NSZeroRect reshapeListener:reshapeListener]; +- (instancetype)initWithShareContext:(NSOpenGLContext*)shareContext + reshapeListener:(id)reshapeListener { + return [self initWithFrame:NSZeroRect shareContext:shareContext reshapeListener:reshapeListener]; } - (instancetype)initWithFrame:(NSRect)frame + shareContext:(NSOpenGLContext*)shareContext reshapeListener:(id)reshapeListener { - NSOpenGLPixelFormatAttribute attributes[] = { - NSOpenGLPFAColorSize, 24, NSOpenGLPFAAlphaSize, 8, NSOpenGLPFADoubleBuffer, 0, - }; - NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; - self = [super initWithFrame:frame pixelFormat:pixelFormat]; + self = [super initWithFrame:frame]; if (self) { + self.openGLContext = [[NSOpenGLContext alloc] initWithFormat:shareContext.pixelFormat + shareContext:shareContext]; _reshapeListener = reshapeListener; self.wantsBestResolutionOpenGLSurface = YES; }