diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e87e78e4dbadd..745ed885b1e65 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -945,6 +945,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDel FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterDartProject.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterHeadlessDartRunner.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h @@ -963,6 +964,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartPro FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 5ec02060f5b7b..a8fa087317c3c 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -436,6 +436,21 @@ Shell::~Shell() { platform_latch.Wait(); } +std::unique_ptr Shell::Spawn( + Settings settings, + const CreateCallback& on_create_platform_view, + const CreateCallback& on_create_rasterizer) { + RunConfiguration configuration = + RunConfiguration::InferFromSettings(settings); + TaskRunners task_runners = task_runners_; + FML_DCHECK(task_runners.IsValid()); + std::unique_ptr result(Shell::Create(std::move(task_runners), settings, + on_create_platform_view, + on_create_rasterizer)); + result->RunEngine(std::move(configuration)); + return result; +} + void Shell::NotifyLowMemoryWarning() const { auto trace_id = fml::tracing::TraceNonce(); TRACE_EVENT_ASYNC_BEGIN0("flutter", "Shell::NotifyLowMemoryWarning", diff --git a/shell/common/shell.h b/shell/common/shell.h index 6ff3a0407bb24..2166450f09656 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -220,6 +220,19 @@ class Shell final : public PlatformView::Delegate, /// ~Shell(); + //---------------------------------------------------------------------------- + /// @brief Creates one Shell from another Shell where the created Shell + /// takes the opportunity to share any internal components it can. + /// This results is a Shell that has a smaller startup time cost + /// and a smaller memory footprint than an Shell created with a + /// Create function. + /// + /// @see http://flutter.dev/go/multiple-engines + std::unique_ptr Spawn( + Settings settings, + const CreateCallback& on_create_platform_view, + const CreateCallback& on_create_rasterizer); + //---------------------------------------------------------------------------- /// @brief Starts an isolate for the given RunConfiguration. /// diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 9fef249047449..c3b0f13c1be2d 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -32,6 +32,7 @@ #include "flutter/shell/common/vsync_waiter_fallback.h" #include "flutter/shell/version/version.h" #include "flutter/testing/testing.h" +#include "gmock/gmock.h" #include "third_party/rapidjson/include/rapidjson/writer.h" #include "third_party/skia/include/core/SkPictureRecorder.h" #include "third_party/tonic/converter/dart_converter.h" @@ -42,6 +43,77 @@ namespace flutter { namespace testing { +namespace { +class MockPlatformViewDelegate : public PlatformView::Delegate { + MOCK_METHOD1(OnPlatformViewCreated, void(std::unique_ptr surface)); + + MOCK_METHOD0(OnPlatformViewDestroyed, void()); + + MOCK_METHOD1(OnPlatformViewSetNextFrameCallback, + void(const fml::closure& closure)); + + MOCK_METHOD1(OnPlatformViewSetViewportMetrics, + void(const ViewportMetrics& metrics)); + + MOCK_METHOD1(OnPlatformViewDispatchPlatformMessage, + void(fml::RefPtr message)); + + MOCK_METHOD1(OnPlatformViewDispatchPointerDataPacket, + void(std::unique_ptr packet)); + + MOCK_METHOD3(OnPlatformViewDispatchSemanticsAction, + void(int32_t id, + SemanticsAction action, + std::vector args)); + + MOCK_METHOD1(OnPlatformViewSetSemanticsEnabled, void(bool enabled)); + + MOCK_METHOD1(OnPlatformViewSetAccessibilityFeatures, void(int32_t flags)); + + MOCK_METHOD1(OnPlatformViewRegisterTexture, + void(std::shared_ptr texture)); + + MOCK_METHOD1(OnPlatformViewUnregisterTexture, void(int64_t texture_id)); + + MOCK_METHOD1(OnPlatformViewMarkTextureFrameAvailable, + void(int64_t texture_id)); + + MOCK_METHOD3(LoadDartDeferredLibrary, + void(intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions)); + + MOCK_METHOD3(LoadDartDeferredLibraryError, + void(intptr_t loading_unit_id, + const std::string error_message, + bool transient)); + + MOCK_METHOD1(UpdateAssetManager, + void(std::shared_ptr asset_manager)); +}; + +class MockSurface : public Surface { + MOCK_METHOD0(IsValid, bool()); + + MOCK_METHOD1(AcquireFrame, + std::unique_ptr(const SkISize& size)); + + MOCK_CONST_METHOD0(GetRootTransformation, SkMatrix()); + + MOCK_METHOD0(GetContext, GrDirectContext*()); + + MOCK_METHOD0(MakeRenderContextCurrent, std::unique_ptr()); + + MOCK_METHOD0(ClearRenderContext, bool()); +}; + +class MockPlatformView : public PlatformView { + public: + MockPlatformView(MockPlatformViewDelegate& delegate, TaskRunners task_runners) + : PlatformView(delegate, task_runners) {} + MOCK_METHOD0(CreateRenderingSurface, std::unique_ptr()); +}; +} // namespace static bool ValidateShell(Shell* shell) { if (!shell) { @@ -2318,5 +2390,52 @@ TEST_F(ShellTest, AssetManagerMulti) { } } +TEST_F(ShellTest, Spawn) { + auto settings = CreateSettingsForFixture(); + auto shell = CreateShell(settings); + ASSERT_TRUE(ValidateShell(shell.get())); + + auto configuration = RunConfiguration::InferFromSettings(settings); + ASSERT_TRUE(configuration.IsValid()); + configuration.SetEntrypoint("fixturesAreFunctionalMain"); + + fml::AutoResetWaitableEvent main_latch; + AddNativeCallback( + "SayHiFromFixturesAreFunctionalMain", + CREATE_NATIVE_ENTRY([&main_latch](auto args) { main_latch.Signal(); })); + + RunEngine(shell.get(), std::move(configuration)); + main_latch.Wait(); + ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + + { + fml::AutoResetWaitableEvent latch; + fml::TaskRunner::RunNowOrPostTask( + shell->GetTaskRunners().GetPlatformTaskRunner(), + [this, &spawner = shell, &latch, settings]() { + MockPlatformViewDelegate platform_view_delegate; + auto spawn = spawner->Spawn( + settings, + [&platform_view_delegate](Shell& shell) { + auto result = std::make_unique( + platform_view_delegate, shell.GetTaskRunners()); + ON_CALL(*result, CreateRenderingSurface()) + .WillByDefault(::testing::Invoke( + [] { return std::make_unique(); })); + return result; + }, + [](Shell& shell) { return std::make_unique(shell); }); + ASSERT_NE(nullptr, spawn.get()); + ASSERT_TRUE(ValidateShell(spawn.get())); + DestroyShell(std::move(spawn)); + latch.Signal(); + }); + latch.Wait(); + } + + DestroyShell(std::move(shell)); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 4087f88b6715a..b964b191d6958 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -28,6 +28,7 @@ _flutter_framework_headers = [ "framework/Headers/FlutterCallbackCache.h", "framework/Headers/FlutterDartProject.h", "framework/Headers/FlutterEngine.h", + "framework/Headers/FlutterEngineGroup.h", "framework/Headers/FlutterHeadlessDartRunner.h", "framework/Headers/FlutterPlatformViews.h", "framework/Headers/FlutterPlugin.h", @@ -52,6 +53,7 @@ source_set("flutter_framework_source") { "framework/Source/FlutterDartProject.mm", "framework/Source/FlutterDartProject_Internal.h", "framework/Source/FlutterEngine.mm", + "framework/Source/FlutterEngineGroup.mm", "framework/Source/FlutterEngine_Internal.h", "framework/Source/FlutterHeadlessDartRunner.mm", "framework/Source/FlutterObservatoryPublisher.h", @@ -215,6 +217,7 @@ shared_library("ios_test_flutter") { "framework/Source/FlutterAppDelegateTest.mm", "framework/Source/FlutterBinaryMessengerRelayTest.mm", "framework/Source/FlutterDartProjectTest.mm", + "framework/Source/FlutterEngineGroupTest.mm", "framework/Source/FlutterEngineTest.mm", "framework/Source/FlutterPluginAppLifeCycleDelegateTest.m", "framework/Source/FlutterTextInputPluginTest.m", diff --git a/shell/platform/darwin/ios/framework/Headers/Flutter.h b/shell/platform/darwin/ios/framework/Headers/Flutter.h index 96409628029fd..cd59a82016d6f 100644 --- a/shell/platform/darwin/ios/framework/Headers/Flutter.h +++ b/shell/platform/darwin/ios/framework/Headers/Flutter.h @@ -12,6 +12,7 @@ #import "FlutterCodecs.h" #import "FlutterDartProject.h" #import "FlutterEngine.h" +#import "FlutterEngineGroup.h" #import "FlutterHeadlessDartRunner.h" #import "FlutterMacros.h" #import "FlutterPlatformViews.h" diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h b/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h new file mode 100644 index 0000000000000..82b681c03c63d --- /dev/null +++ b/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h @@ -0,0 +1,45 @@ +// 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 + +#import "FlutterEngine.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Represents a collection of FlutterEngines who share resources which allows + * them to be created with less time const and occupy less memory than just + * creating multiple FlutterEngines. + * + * Deleting a FlutterEngineGroup doesn't invalidate existing FlutterEngines, but + * it eliminates the possibility to create more FlutterEngines in that group. + * + * @warning This class is a work-in-progress and may change. + * @see https://github.com/flutter/flutter/issues/72009 + */ +@interface FlutterEngineGroup : NSObject +- (instancetype)init NS_UNAVAILABLE; + +/** + * Initialize a new FlutterEngineGroup. + * + * @param name The name that will present in the threads shared across the + * engines in this group. + * @param project The `FlutterDartProject` that all FlutterEngines in this group + * will be executing. + */ +- (instancetype)initWithName:(NSString*)name + project:(nullable FlutterDartProject*)project NS_DESIGNATED_INITIALIZER; + +/** + * Creates a running `FlutterEngine` that shares components with this group. + * + * @see FlutterEngineGroup + */ +- (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint + libraryURI:(nullable NSString*)libraryURI; +@end + +NS_ASSUME_NONNULL_END diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 5ece908bf5993..857ad0e486a4d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -34,6 +34,7 @@ NSString* const FlutterDefaultDartEntrypoint = nil; NSString* const FlutterDefaultInitialRoute = nil; +NSString* const FlutterEngineWillDealloc = @"FlutterEngineWillDealloc"; static constexpr int kNumProfilerSamplesPerSec = 5; @interface FlutterEngineRegistrar : NSObject @@ -54,7 +55,7 @@ @interface FlutterEngine () @implementation FlutterEngine { fml::scoped_nsobject _dartProject; - flutter::ThreadHost _threadHost; + std::shared_ptr _threadHost; std::unique_ptr _shell; NSString* _labelPrefix; std::unique_ptr> _weakFactory; @@ -64,8 +65,8 @@ @implementation FlutterEngine { std::shared_ptr _platformViewsController; flutter::IOSRenderingAPI _renderingApi; - std::unique_ptr _profiler_metrics; - std::unique_ptr _profiler; + std::shared_ptr _profiler_metrics; + std::shared_ptr _profiler; // Channels fml::scoped_nsobject _platformPlugin; @@ -181,6 +182,10 @@ - (void)dealloc { } }]; + [[NSNotificationCenter defaultCenter] postNotificationName:FlutterEngineWillDealloc + object:self + userInfo:nil]; + /// nil out weak references. [_registrars enumerateKeysAndObjectsUsingBlock:^(id key, FlutterEngineRegistrar* registrar, BOOL* stop) { @@ -306,7 +311,7 @@ - (void)destroyContext { self.isolateId = nil; _shell.reset(); _profiler.reset(); - _threadHost.Reset(); + _threadHost.reset(); _platformViewsController.reset(); } @@ -368,10 +373,10 @@ - (void)resetChannels { } - (void)startProfiler { - FML_DCHECK(!_threadHost.name_prefix.empty()); - _profiler_metrics = std::make_unique(); - _profiler = std::make_unique( - _threadHost.name_prefix.c_str(), _threadHost.profiler_thread->GetTaskRunner(), + FML_DCHECK(!_threadHost->name_prefix.empty()); + _profiler_metrics = std::make_shared(); + _profiler = std::make_shared( + _threadHost->name_prefix.c_str(), _threadHost->profiler_thread->GetTaskRunner(), [self]() { return self->_profiler_metrics->GenerateSample(); }, kNumProfilerSamplesPerSec); _profiler->Start(); } @@ -522,6 +527,20 @@ + (NSString*)generateThreadLabel:(NSString*)labelPrefix { threadHostType}; } +static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSString* libraryURI) { + if (libraryURI) { + FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library"; + settings->advisory_script_entrypoint = entrypoint.UTF8String; + settings->advisory_script_uri = libraryURI.UTF8String; + } else if (entrypoint) { + settings->advisory_script_entrypoint = entrypoint.UTF8String; + settings->advisory_script_uri = std::string("main.dart"); + } else { + settings->advisory_script_entrypoint = std::string("main"); + settings->advisory_script_uri = std::string("main.dart"); + } +} + - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI initialRoute:(NSString*)initialRoute { @@ -537,20 +556,11 @@ - (BOOL)createShell:(NSString*)entrypoint auto platformData = [_dartProject.get() defaultPlatformData]; - if (libraryURI) { - FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library"; - settings.advisory_script_entrypoint = entrypoint.UTF8String; - settings.advisory_script_uri = libraryURI.UTF8String; - } else if (entrypoint) { - settings.advisory_script_entrypoint = entrypoint.UTF8String; - settings.advisory_script_uri = std::string("main.dart"); - } else { - settings.advisory_script_entrypoint = std::string("main"); - settings.advisory_script_uri = std::string("main.dart"); - } + SetEntryPoint(&settings, entrypoint, libraryURI); NSString* threadLabel = [FlutterEngine generateThreadLabel:_labelPrefix]; - _threadHost = [FlutterEngine makeThreadHost:threadLabel]; + _threadHost = std::make_shared(); + *_threadHost = [FlutterEngine makeThreadHost:threadLabel]; // Lambda captures by pointers to ObjC objects are fine here because the // create call is synchronous. @@ -566,9 +576,9 @@ - (BOOL)createShell:(NSString*)entrypoint flutter::TaskRunners task_runners(threadLabel.UTF8String, // label fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform - _threadHost.raster_thread->GetTaskRunner(), // raster - _threadHost.ui_thread->GetTaskRunner(), // ui - _threadHost.io_thread->GetTaskRunner() // io + _threadHost->raster_thread->GetTaskRunner(), // raster + _threadHost->ui_thread->GetTaskRunner(), // ui + _threadHost->io_thread->GetTaskRunner() // io ); // Create the shell. This is a blocking operation. @@ -921,6 +931,40 @@ - (void)waitForFirstFrame:(NSTimeInterval)timeout }); } +- (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint + libraryURI:(/*nullable*/ NSString*)libraryURI { + NSAssert(_shell, @"Spawning from an engine without a shell (possibly not run)."); + FlutterEngine* result = [[FlutterEngine alloc] initWithName:_labelPrefix + project:_dartProject.get() + allowHeadlessExecution:_allowHeadlessExecution]; + + flutter::Settings settings = _shell->GetSettings(); + SetEntryPoint(&settings, entrypoint, libraryURI); + + flutter::Shell::CreateCallback on_create_platform_view = + [self](flutter::Shell& shell) { + [self recreatePlatformViewController]; + return std::make_unique( + shell, self->_renderingApi, self->_platformViewsController, shell.GetTaskRunners()); + }; + + flutter::Shell::CreateCallback on_create_rasterizer = + [](flutter::Shell& shell) { return std::make_unique(shell); }; + + std::unique_ptr shell = + _shell->Spawn(std::move(settings), on_create_platform_view, on_create_rasterizer); + + result->_threadHost = _threadHost; + result->_profiler = _profiler; + result->_profiler_metrics = _profiler_metrics; + [result setupShell:std::move(shell) withObservatoryPublication:NO]; + return result; +} + +- (const flutter::ThreadHost&)threadHost { + return *_threadHost; +} + @end @implementation FlutterEngineRegistrar { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm new file mode 100644 index 0000000000000..8b12094a4c4de --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm @@ -0,0 +1,62 @@ +// 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/ios/framework/Headers/FlutterEngineGroup.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" + +@interface FlutterEngineGroup () +@property(nonatomic, copy) NSString* name; +@property(nonatomic, strong) NSMutableArray* engines; +@property(nonatomic, strong) FlutterDartProject* project; +@end + +@implementation FlutterEngineGroup { + int _enginesCreatedCount; +} + +- (instancetype)initWithName:(NSString*)name project:(nullable FlutterDartProject*)project { + self = [super init]; + if (self) { + self.name = name; + self.engines = [[NSMutableArray alloc] init]; + self.project = project; + } + return self; +} + +- (void)dealloc { + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center removeObserver:self]; + [_name release]; + [_engines release]; + [super dealloc]; +} + +- (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint + libraryURI:(nullable NSString*)libraryURI { + NSString* engineName = [NSString stringWithFormat:@"%@.%d", self.name, ++_enginesCreatedCount]; + FlutterEngine* engine; + if (self.engines.count <= 0) { + engine = [[FlutterEngine alloc] initWithName:engineName project:self.project]; + [engine runWithEntrypoint:entrypoint libraryURI:libraryURI]; + } else { + FlutterEngine* spawner = (FlutterEngine*)[self.engines[0] pointerValue]; + engine = [spawner spawnWithEntrypoint:entrypoint libraryURI:libraryURI]; + } + [_engines addObject:[NSValue valueWithPointer:engine]]; + + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center addObserver:self + selector:@selector(onEngineWillBeDealloced:) + name:FlutterEngineWillDealloc + object:engine]; + + return [engine autorelease]; +} + +- (void)onEngineWillBeDealloced:(NSNotification*)notification { + [_engines removeObject:[NSValue valueWithPointer:notification.object]]; +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm new file mode 100644 index 0000000000000..dd34a667f80c1 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm @@ -0,0 +1,43 @@ +// 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 +#import + +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h" + +FLUTTER_ASSERT_ARC + +@interface FlutterEngineGroupTest : XCTestCase +@end + +@implementation FlutterEngineGroupTest + +- (void)testMake { + FlutterEngineGroup* group = [[FlutterEngineGroup alloc] initWithName:@"foo" project:nil]; + FlutterEngine* engine = [group makeEngineWithEntrypoint:nil libraryURI:nil]; + XCTAssertNotNil(engine); +} + +- (void)testSpawn { + FlutterEngineGroup* group = [[FlutterEngineGroup alloc] initWithName:@"foo" project:nil]; + FlutterEngine* spawner = [group makeEngineWithEntrypoint:nil libraryURI:nil]; + FlutterEngine* spawnee = [group makeEngineWithEntrypoint:nil libraryURI:nil]; + XCTAssertNotNil(spawner); + XCTAssertNotNil(spawnee); + XCTAssertEqual(&spawner.threadHost, &spawnee.threadHost); +} + +- (void)testDeleteLastEngine { + FlutterEngineGroup* group = [[FlutterEngineGroup alloc] initWithName:@"foo" project:nil]; + @autoreleasepool { + FlutterEngine* spawner = [group makeEngineWithEntrypoint:nil libraryURI:nil]; + XCTAssertNotNil(spawner); + } + FlutterEngine* spawnee = [group makeEngineWithEntrypoint:nil libraryURI:nil]; + XCTAssertNotNil(spawnee); +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 3684ea5374a60..dd63da24de014 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -134,4 +134,28 @@ - (void)testWaitForFirstFrameTimeout { [self waitForExpectationsWithTimeout:1 handler:nil]; } +- (void)testSpawn { + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; + [engine run]; + FlutterEngine* spawn = [engine spawnWithEntrypoint:nil libraryURI:nil]; + XCTAssertNotNil(spawn); +} + +- (void)testDeallocNotification { + XCTestExpectation* deallocNotification = [self expectationWithDescription:@"deallocNotification"]; + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + id observer; + @autoreleasepool { + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; + observer = [center addObserverForName:FlutterEngineWillDealloc + object:engine + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification* note) { + [deallocNotification fulfill]; + }]; + } + [self waitForExpectationsWithTimeout:1 handler:nil]; + [center removeObserver:observer]; +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index 85a82f3891530..36c3b2c433834 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -22,6 +22,8 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" +extern NSString* const FlutterEngineWillDealloc; + @interface FlutterEngine () - (flutter::Shell&)shell; @@ -49,6 +51,16 @@ - (flutter::PlatformViewIOS*)iosPlatformView; - (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback; + +/** + * Creates one running FlutterEngine from another, sharing components between them. + * + * This results in a faster creation time and a smaller memory footprint engine. + * This should only be called on a FlutterEngine that is running. + */ +- (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint + libraryURI:(/*nullable*/ NSString*)libraryURI; + @end #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERENGINE_INTERNAL_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index 50db832916ea4..e241d22b1e8da 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h @@ -5,11 +5,20 @@ #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" +extern NSString* const FlutterEngineWillDealloc; + @class FlutterBinaryMessengerRelay; +namespace flutter { +class ThreadHost; +} + // Category to add test-only visibility. @interface FlutterEngine (Test) - (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger; - (flutter::IOSRenderingAPI)platformViewsRenderingAPI; - (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback; +- (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint + libraryURI:(/*nullable*/ NSString*)libraryURI; +- (const flutter::ThreadHost&)threadHost; @end diff --git a/tools/gen_objcdoc.sh b/tools/gen_objcdoc.sh index abeb10980425a..6d51a5553264d 100755 --- a/tools/gen_objcdoc.sh +++ b/tools/gen_objcdoc.sh @@ -47,6 +47,7 @@ FlutterCallbackCache.html FlutterCallbackInformation.html FlutterDartProject.html FlutterEngine.html +FlutterEngineGroup.html FlutterError.html FlutterEventChannel.html FlutterHeadlessDartRunner.html