Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
15 changes: 15 additions & 0 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,21 @@ Shell::~Shell() {
platform_latch.Wait();
}

std::unique_ptr<Shell> Shell::Spawn(
Settings settings,
const CreateCallback<PlatformView>& on_create_platform_view,
const CreateCallback<Rasterizer>& on_create_rasterizer) {
RunConfiguration configuration =
Comment thread
gaaclarke marked this conversation as resolved.
RunConfiguration::InferFromSettings(settings);
TaskRunners task_runners = task_runners_;
FML_DCHECK(task_runners.IsValid());
std::unique_ptr<Shell> 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",
Expand Down
13 changes: 13 additions & 0 deletions shell/common/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 share any internal components it can.
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
/// 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<Shell> Spawn(
Settings settings,
const CreateCallback<PlatformView>& on_create_platform_view,
const CreateCallback<Rasterizer>& on_create_rasterizer);

//----------------------------------------------------------------------------
/// @brief Starts an isolate for the given RunConfiguration.
///
Expand Down
119 changes: 119 additions & 0 deletions shell/common/shell_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -42,6 +43,77 @@

namespace flutter {
namespace testing {
namespace {
class MockPlatformViewDelegate : public PlatformView::Delegate {
MOCK_METHOD1(OnPlatformViewCreated, void(std::unique_ptr<Surface> 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<PlatformMessage> message));

MOCK_METHOD1(OnPlatformViewDispatchPointerDataPacket,
void(std::unique_ptr<PointerDataPacket> packet));

MOCK_METHOD3(OnPlatformViewDispatchSemanticsAction,
void(int32_t id,
SemanticsAction action,
std::vector<uint8_t> args));

MOCK_METHOD1(OnPlatformViewSetSemanticsEnabled, void(bool enabled));

MOCK_METHOD1(OnPlatformViewSetAccessibilityFeatures, void(int32_t flags));

MOCK_METHOD1(OnPlatformViewRegisterTexture,
void(std::shared_ptr<Texture> 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<const fml::Mapping> snapshot_data,
std::unique_ptr<const fml::Mapping> 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<AssetManager> asset_manager));
};

class MockSurface : public Surface {
MOCK_METHOD0(IsValid, bool());

MOCK_METHOD1(AcquireFrame,
std::unique_ptr<SurfaceFrame>(const SkISize& size));

MOCK_CONST_METHOD0(GetRootTransformation, SkMatrix());

MOCK_METHOD0(GetContext, GrDirectContext*());

MOCK_METHOD0(MakeRenderContextCurrent, std::unique_ptr<GLContextResult>());

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<Surface>());
};
} // namespace

static bool ValidateShell(Shell* shell) {
if (!shell) {
Expand Down Expand Up @@ -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<MockPlatformView>(
platform_view_delegate, shell.GetTaskRunners());
ON_CALL(*result, CreateRenderingSurface())
.WillByDefault(::testing::Invoke(
[] { return std::make_unique<MockSurface>(); }));
return result;
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
ASSERT_NE(nullptr, spawn.get());
Comment thread
gaaclarke marked this conversation as resolved.
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
3 changes: 3 additions & 0 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions shell/platform/darwin/ios/framework/Headers/Flutter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
25 changes: 25 additions & 0 deletions shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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 <Foundation/Foundation.h>

#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.
Comment thread
gaaclarke marked this conversation as resolved.
*
* @see http://flutter.dev/go/multiple-engines
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
*/
@interface FlutterEngineGroup : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithName:(NSString*)name
project:(nullable FlutterDartProject*)project NS_DESIGNATED_INITIALIZER;
- (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint;
@end

NS_ASSUME_NONNULL_END
66 changes: 54 additions & 12 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

NSString* const FlutterDefaultDartEntrypoint = nil;
NSString* const FlutterDefaultInitialRoute = nil;
NSString* const FlutterEngineWillDealloc = @"FlutterEngineWillDealloc";
static constexpr int kNumProfilerSamplesPerSec = 5;

@interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
Expand All @@ -54,7 +55,7 @@ @interface FlutterEngine () <FlutterTextInputDelegate, FlutterBinaryMessenger>

@implementation FlutterEngine {
fml::scoped_nsobject<FlutterDartProject> _dartProject;
flutter::ThreadHost _threadHost;
std::shared_ptr<flutter::ThreadHost> _threadHost;
std::unique_ptr<flutter::Shell> _shell;
NSString* _labelPrefix;
std::unique_ptr<fml::WeakPtrFactory<FlutterEngine>> _weakFactory;
Expand All @@ -64,8 +65,8 @@ @implementation FlutterEngine {

std::shared_ptr<flutter::FlutterPlatformViewsController> _platformViewsController;
flutter::IOSRenderingAPI _renderingApi;
std::unique_ptr<flutter::ProfilerMetricsIOS> _profiler_metrics;
std::unique_ptr<flutter::SamplingProfiler> _profiler;
std::shared_ptr<flutter::ProfilerMetricsIOS> _profiler_metrics;
std::shared_ptr<flutter::SamplingProfiler> _profiler;

// Channels
fml::scoped_nsobject<FlutterPlatformPlugin> _platformPlugin;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -306,7 +311,7 @@ - (void)destroyContext {
self.isolateId = nil;
_shell.reset();
_profiler.reset();
_threadHost.Reset();
_threadHost.reset();
_platformViewsController.reset();
}

Expand Down Expand Up @@ -368,10 +373,10 @@ - (void)resetChannels {
}

- (void)startProfiler {
FML_DCHECK(!_threadHost.name_prefix.empty());
_profiler_metrics = std::make_unique<flutter::ProfilerMetricsIOS>();
_profiler = std::make_unique<flutter::SamplingProfiler>(
_threadHost.name_prefix.c_str(), _threadHost.profiler_thread->GetTaskRunner(),
FML_DCHECK(!_threadHost->name_prefix.empty());
_profiler_metrics = std::make_shared<flutter::ProfilerMetricsIOS>();
_profiler = std::make_shared<flutter::SamplingProfiler>(
_threadHost->name_prefix.c_str(), _threadHost->profiler_thread->GetTaskRunner(),
Comment thread
gaaclarke marked this conversation as resolved.
[self]() { return self->_profiler_metrics->GenerateSample(); }, kNumProfilerSamplesPerSec);
_profiler->Start();
}
Expand Down Expand Up @@ -550,7 +555,8 @@ - (BOOL)createShell:(NSString*)entrypoint
}

NSString* threadLabel = [FlutterEngine generateThreadLabel:_labelPrefix];
_threadHost = [FlutterEngine makeThreadHost:threadLabel];
_threadHost = std::make_shared<flutter::ThreadHost>();
*_threadHost = [FlutterEngine makeThreadHost:threadLabel];

// Lambda captures by pointers to ObjC objects are fine here because the
// create call is synchronous.
Expand All @@ -566,9 +572,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.
Expand Down Expand Up @@ -921,6 +927,42 @@ - (void)waitForFirstFrame:(NSTimeInterval)timeout
});
}

- (FlutterEngine*)spawnWithEntrypoint:(NSString*)entrypoint {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment? if you add new features to createShell, please make sure it's mirrored here, and vice versa.

Could add some contextual reasoning too. We could merge it into the same API except the Dart APIs only support creating Isolates from other Isolates. Here's a dart SDK github issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A change in called functions will often require a change in caller functions. I'm not sure it's worth point that out. I don't know code comments is the right place for design decisions. If you want to suggest something specific we can put it in there.

assert(_shell);
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
FlutterEngine* result =
[[FlutterEngine alloc] initWithName:[_labelPrefix stringByAppendingString:@"-spawn"]
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
project:_dartProject.get()
allowHeadlessExecution:_allowHeadlessExecution];

flutter::Settings settings = _shell->GetSettings();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extract parts of this into a private method to share between here and createShell? To minimize the risk of someone updating one and not the other as much as possible?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I did that in setupShell:withObservatoryPublication: and SetEntryPoint. There is very little happening here, mostly boilerplate around lambdas.

if (entrypoint) {
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
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");
}

flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
[self](flutter::Shell& shell) {
[self recreatePlatformViewController];
return std::make_unique<flutter::PlatformViewIOS>(
shell, self->_renderingApi, self->_platformViewsController, shell.GetTaskRunners());
};

flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
[](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); };

std::unique_ptr<flutter::Shell> shell =
_shell->Spawn(std::move(settings), on_create_platform_view, on_create_rasterizer);

result->_threadHost = _threadHost;
Comment thread
gaaclarke marked this conversation as resolved.
result->_profiler = _profiler;
result->_profiler_metrics = _profiler_metrics;
[result setupShell:std::move(shell) withObservatoryPublication:NO];
return result;
}

@end

@implementation FlutterEngineRegistrar {
Expand Down
Loading