mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
681 lines
24 KiB
C++
681 lines
24 KiB
C++
// 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.
|
|
|
|
#define FML_USED_ON_EMBEDDER
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
|
|
#include "flutter/assets/asset_manager.h"
|
|
#include "flutter/assets/directory_asset_bundle.h"
|
|
#include "flutter/flow/embedded_views.h"
|
|
#include "flutter/fml/build_config.h"
|
|
#include "flutter/fml/file.h"
|
|
#include "flutter/fml/make_copyable.h"
|
|
#include "flutter/fml/message_loop.h"
|
|
#include "flutter/fml/paths.h"
|
|
#include "flutter/fml/synchronization/waitable_event.h"
|
|
#include "flutter/fml/task_runner.h"
|
|
#include "flutter/shell/common/platform_view.h"
|
|
#include "flutter/shell/common/rasterizer.h"
|
|
#include "flutter/shell/common/shell.h"
|
|
#include "flutter/shell/common/switches.h"
|
|
#include "flutter/shell/common/thread_host.h"
|
|
#include "flutter/shell/gpu/gpu_surface_software.h"
|
|
#include "flutter/third_party/abseil-cpp/absl/base/no_destructor.h"
|
|
|
|
#include "third_party/dart/runtime/include/bin/dart_io_api.h"
|
|
#include "third_party/dart/runtime/include/dart_api.h"
|
|
#include "third_party/skia/include/core/SkSurface.h"
|
|
|
|
// Impeller should only be enabled if the Vulkan backend is enabled.
|
|
#define ALLOW_IMPELLER (IMPELLER_SUPPORTS_RENDERING && IMPELLER_ENABLE_VULKAN)
|
|
|
|
#if ALLOW_IMPELLER
|
|
#include <vulkan/vulkan.h> // nogncheck
|
|
#include "impeller/entity/vk/entity_shaders_vk.h" // nogncheck
|
|
#include "impeller/entity/vk/framebuffer_blend_shaders_vk.h" // nogncheck
|
|
#include "impeller/entity/vk/modern_shaders_vk.h" // nogncheck
|
|
#include "impeller/renderer/backend/vulkan/context_vk.h" // nogncheck
|
|
#include "impeller/renderer/backend/vulkan/surface_context_vk.h" // nogncheck
|
|
#include "impeller/renderer/context.h" // nogncheck
|
|
#include "impeller/renderer/vk/compute_shaders_vk.h" // nogncheck
|
|
#include "shell/gpu/gpu_surface_vulkan_impeller.h" // nogncheck
|
|
|
|
static std::vector<std::shared_ptr<fml::Mapping>> ShaderLibraryMappings() {
|
|
return {
|
|
std::make_shared<fml::NonOwnedMapping>(impeller_entity_shaders_vk_data,
|
|
impeller_entity_shaders_vk_length),
|
|
std::make_shared<fml::NonOwnedMapping>(impeller_modern_shaders_vk_data,
|
|
impeller_modern_shaders_vk_length),
|
|
std::make_shared<fml::NonOwnedMapping>(
|
|
impeller_framebuffer_blend_shaders_vk_data,
|
|
impeller_framebuffer_blend_shaders_vk_length),
|
|
std::make_shared<fml::NonOwnedMapping>(
|
|
impeller_compute_shaders_vk_data, impeller_compute_shaders_vk_length),
|
|
};
|
|
}
|
|
|
|
struct ImpellerVulkanContextHolder {
|
|
ImpellerVulkanContextHolder() = default;
|
|
ImpellerVulkanContextHolder(ImpellerVulkanContextHolder&&) = default;
|
|
std::shared_ptr<impeller::ContextVK> context;
|
|
std::shared_ptr<impeller::SurfaceContextVK> surface_context;
|
|
|
|
bool Initialize(bool enable_validation);
|
|
};
|
|
|
|
bool ImpellerVulkanContextHolder::Initialize(bool enable_validation) {
|
|
impeller::ContextVK::Settings context_settings;
|
|
context_settings.proc_address_callback = &vkGetInstanceProcAddr;
|
|
context_settings.shader_libraries_data = ShaderLibraryMappings();
|
|
context_settings.cache_directory = fml::paths::GetCachesDirectory();
|
|
context_settings.enable_validation = enable_validation;
|
|
|
|
context = impeller::ContextVK::Create(std::move(context_settings));
|
|
if (!context || !context->IsValid()) {
|
|
VALIDATION_LOG << "Could not create Vulkan context.";
|
|
return false;
|
|
}
|
|
|
|
impeller::vk::SurfaceKHR vk_surface;
|
|
impeller::vk::HeadlessSurfaceCreateInfoEXT surface_create_info;
|
|
auto res = context->GetInstance().createHeadlessSurfaceEXT(
|
|
&surface_create_info, // surface create info
|
|
nullptr, // allocator
|
|
&vk_surface // surface
|
|
);
|
|
if (res != impeller::vk::Result::eSuccess) {
|
|
VALIDATION_LOG << "Could not create surface for tester "
|
|
<< impeller::vk::to_string(res);
|
|
return false;
|
|
}
|
|
|
|
impeller::vk::UniqueSurfaceKHR surface{vk_surface, context->GetInstance()};
|
|
surface_context = context->CreateSurfaceContext();
|
|
if (!surface_context->SetWindowSurface(std::move(surface),
|
|
impeller::ISize{1, 1})) {
|
|
VALIDATION_LOG << "Could not set up surface for context.";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#else
|
|
struct ImpellerVulkanContextHolder {};
|
|
#endif // IMPELLER_SUPPORTS_RENDERING
|
|
|
|
#if defined(FML_OS_WIN)
|
|
#include <combaseapi.h>
|
|
#endif // defined(FML_OS_WIN)
|
|
|
|
#if defined(FML_OS_POSIX)
|
|
#include <signal.h>
|
|
#endif // defined(FML_OS_POSIX)
|
|
|
|
namespace flutter {
|
|
|
|
static absl::NoDestructor<std::unique_ptr<Shell>> g_shell;
|
|
|
|
static constexpr int64_t kImplicitViewId = 0ll;
|
|
|
|
static void ConfigureShell(Shell* shell) {
|
|
auto device_pixel_ratio = 3.0;
|
|
auto physical_width = 2400.0; // 800 at 3x resolution.
|
|
auto physical_height = 1800.0; // 600 at 3x resolution.
|
|
|
|
std::vector<std::unique_ptr<Display>> displays;
|
|
displays.push_back(std::make_unique<Display>(
|
|
0, 60, physical_width, physical_height, device_pixel_ratio));
|
|
shell->OnDisplayUpdates(std::move(displays));
|
|
|
|
flutter::ViewportMetrics metrics{};
|
|
metrics.device_pixel_ratio = device_pixel_ratio;
|
|
metrics.physical_width = physical_width;
|
|
metrics.physical_height = physical_height;
|
|
metrics.display_id = 0;
|
|
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId, metrics);
|
|
}
|
|
|
|
class TesterExternalViewEmbedder : public ExternalViewEmbedder {
|
|
// |ExternalViewEmbedder|
|
|
DlCanvas* GetRootCanvas() override { return nullptr; }
|
|
|
|
// |ExternalViewEmbedder|
|
|
void CancelFrame() override {}
|
|
|
|
// |ExternalViewEmbedder|
|
|
void BeginFrame(GrDirectContext* context,
|
|
const fml::RefPtr<fml::RasterThreadMerger>&
|
|
raster_thread_merger) override {}
|
|
|
|
// |ExternalViewEmbedder|
|
|
void PrepareFlutterView(SkISize frame_size,
|
|
double device_pixel_ratio) override {}
|
|
|
|
// |ExternalViewEmbedder|
|
|
void PrerollCompositeEmbeddedView(
|
|
int64_t view_id,
|
|
std::unique_ptr<EmbeddedViewParams> params) override {}
|
|
|
|
// |ExternalViewEmbedder|
|
|
DlCanvas* CompositeEmbeddedView(int64_t view_id) override {
|
|
return &builder_;
|
|
}
|
|
|
|
private:
|
|
DisplayListBuilder builder_;
|
|
};
|
|
|
|
class TesterGPUSurfaceSoftware : public GPUSurfaceSoftware {
|
|
public:
|
|
TesterGPUSurfaceSoftware(GPUSurfaceSoftwareDelegate* delegate,
|
|
bool render_to_surface)
|
|
: GPUSurfaceSoftware(delegate, render_to_surface) {}
|
|
|
|
bool EnableRasterCache() const override { return false; }
|
|
};
|
|
|
|
class TesterPlatformView : public PlatformView,
|
|
public GPUSurfaceSoftwareDelegate {
|
|
public:
|
|
TesterPlatformView(Delegate& delegate,
|
|
const TaskRunners& task_runners,
|
|
ImpellerVulkanContextHolder&& impeller_context_holder)
|
|
: PlatformView(delegate, task_runners),
|
|
impeller_context_holder_(std::move(impeller_context_holder)) {}
|
|
|
|
~TesterPlatformView() {
|
|
#if ALLOW_IMPELLER
|
|
if (impeller_context_holder_.context) {
|
|
impeller_context_holder_.context->Shutdown();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// |PlatformView|
|
|
std::shared_ptr<impeller::Context> GetImpellerContext() const override {
|
|
#if ALLOW_IMPELLER
|
|
return std::static_pointer_cast<impeller::Context>(
|
|
impeller_context_holder_.context);
|
|
#else
|
|
return nullptr;
|
|
#endif // ALLOW_IMPELLER
|
|
}
|
|
|
|
// |PlatformView|
|
|
std::unique_ptr<Surface> CreateRenderingSurface() override {
|
|
#if ALLOW_IMPELLER
|
|
if (delegate_.OnPlatformViewGetSettings().enable_impeller) {
|
|
FML_DCHECK(impeller_context_holder_.context);
|
|
auto surface = std::make_unique<GPUSurfaceVulkanImpeller>(
|
|
nullptr, impeller_context_holder_.surface_context);
|
|
FML_DCHECK(surface->IsValid());
|
|
return surface;
|
|
}
|
|
#endif // ALLOW_IMPELLER
|
|
auto surface = std::make_unique<TesterGPUSurfaceSoftware>(
|
|
this, true /* render to surface */);
|
|
FML_DCHECK(surface->IsValid());
|
|
return surface;
|
|
}
|
|
|
|
// |GPUSurfaceSoftwareDelegate|
|
|
sk_sp<SkSurface> AcquireBackingStore(const SkISize& size) override {
|
|
if (sk_surface_ != nullptr &&
|
|
SkISize::Make(sk_surface_->width(), sk_surface_->height()) == size) {
|
|
// The old and new surface sizes are the same. Nothing to do here.
|
|
return sk_surface_;
|
|
}
|
|
|
|
SkImageInfo info =
|
|
SkImageInfo::MakeN32(size.fWidth, size.fHeight, kPremul_SkAlphaType,
|
|
SkColorSpace::MakeSRGB());
|
|
sk_surface_ = SkSurfaces::Raster(info, nullptr);
|
|
|
|
if (sk_surface_ == nullptr) {
|
|
FML_LOG(ERROR)
|
|
<< "Could not create backing store for software rendering.";
|
|
return nullptr;
|
|
}
|
|
|
|
return sk_surface_;
|
|
}
|
|
|
|
// |GPUSurfaceSoftwareDelegate|
|
|
bool PresentBackingStore(sk_sp<SkSurface> backing_store) override {
|
|
return true;
|
|
}
|
|
|
|
// |PlatformView|
|
|
std::shared_ptr<ExternalViewEmbedder> CreateExternalViewEmbedder() override {
|
|
return external_view_embedder_;
|
|
}
|
|
|
|
private:
|
|
sk_sp<SkSurface> sk_surface_ = nullptr;
|
|
[[maybe_unused]] ImpellerVulkanContextHolder impeller_context_holder_;
|
|
std::shared_ptr<TesterExternalViewEmbedder> external_view_embedder_ =
|
|
std::make_shared<TesterExternalViewEmbedder>();
|
|
};
|
|
|
|
// Checks whether the engine's main Dart isolate has no pending work. If so,
|
|
// then exit the given message loop.
|
|
class ScriptCompletionTaskObserver {
|
|
public:
|
|
ScriptCompletionTaskObserver(Shell& shell,
|
|
fml::RefPtr<fml::TaskRunner> main_task_runner,
|
|
bool run_forever)
|
|
: shell_(shell),
|
|
main_task_runner_(std::move(main_task_runner)),
|
|
run_forever_(run_forever) {}
|
|
|
|
int GetExitCodeForLastError() const {
|
|
return static_cast<int>(last_error_.value_or(DartErrorCode::NoError));
|
|
}
|
|
|
|
void DidProcessTask() {
|
|
last_error_ = shell_.GetUIIsolateLastError();
|
|
if (shell_.EngineHasLivePorts()) {
|
|
// The UI isolate still has live ports and is running. Nothing to do
|
|
// just yet.
|
|
return;
|
|
}
|
|
|
|
if (run_forever_) {
|
|
// We need this script to run forever. We have already recorded the last
|
|
// error. Keep going.
|
|
return;
|
|
}
|
|
|
|
if (!has_terminated_) {
|
|
// Only try to terminate the loop once.
|
|
has_terminated_ = true;
|
|
fml::TaskRunner::RunNowOrPostTask(main_task_runner_, []() {
|
|
fml::MessageLoop::GetCurrent().Terminate();
|
|
});
|
|
}
|
|
}
|
|
|
|
private:
|
|
Shell& shell_;
|
|
fml::RefPtr<fml::TaskRunner> main_task_runner_;
|
|
bool run_forever_ = false;
|
|
std::optional<DartErrorCode> last_error_;
|
|
bool has_terminated_ = false;
|
|
|
|
FML_DISALLOW_COPY_AND_ASSIGN(ScriptCompletionTaskObserver);
|
|
};
|
|
|
|
// Processes spawned via dart:io inherit their signal handling from the parent
|
|
// process. As part of spawning, the spawner blocks signals temporarily, so we
|
|
// need to explicitly unblock the signals we care about in the new process. In
|
|
// particular, we need to unblock SIGPROF for CPU profiling to work on the
|
|
// mutator thread in the main isolate in this process (threads spawned by the VM
|
|
// know about this limitation and automatically have this signal unblocked).
|
|
static void UnblockSIGPROF() {
|
|
#if defined(FML_OS_POSIX)
|
|
sigset_t set;
|
|
sigemptyset(&set);
|
|
sigaddset(&set, SIGPROF);
|
|
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
|
|
#endif // defined(FML_OS_POSIX)
|
|
}
|
|
|
|
int RunTester(const flutter::Settings& settings,
|
|
bool run_forever,
|
|
bool multithreaded) {
|
|
const auto thread_label = "io.flutter.test.";
|
|
|
|
// Necessary if we want to use the CPU profiler on the main isolate's mutator
|
|
// thread.
|
|
//
|
|
// OSX WARNING: avoid spawning additional threads before this call due to a
|
|
// kernel bug that may enable SIGPROF on an unintended thread in the process.
|
|
UnblockSIGPROF();
|
|
|
|
fml::MessageLoop::EnsureInitializedForCurrentThread();
|
|
|
|
auto current_task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
|
|
|
|
std::unique_ptr<ThreadHost> threadhost;
|
|
fml::RefPtr<fml::TaskRunner> platform_task_runner;
|
|
fml::RefPtr<fml::TaskRunner> raster_task_runner;
|
|
fml::RefPtr<fml::TaskRunner> ui_task_runner;
|
|
fml::RefPtr<fml::TaskRunner> io_task_runner;
|
|
|
|
if (multithreaded) {
|
|
threadhost = std::make_unique<ThreadHost>(
|
|
thread_label, ThreadHost::Type::kPlatform | ThreadHost::Type::kIo |
|
|
ThreadHost::Type::kUi | ThreadHost::Type::kRaster);
|
|
platform_task_runner = current_task_runner;
|
|
raster_task_runner = threadhost->raster_thread->GetTaskRunner();
|
|
ui_task_runner = threadhost->ui_thread->GetTaskRunner();
|
|
io_task_runner = threadhost->io_thread->GetTaskRunner();
|
|
} else {
|
|
platform_task_runner = raster_task_runner = ui_task_runner =
|
|
io_task_runner = current_task_runner;
|
|
}
|
|
|
|
const flutter::TaskRunners task_runners(thread_label, // dart thread label
|
|
platform_task_runner, // platform
|
|
raster_task_runner, // raster
|
|
ui_task_runner, // ui
|
|
io_task_runner // io
|
|
);
|
|
|
|
ImpellerVulkanContextHolder impeller_context_holder;
|
|
|
|
#if ALLOW_IMPELLER
|
|
if (settings.enable_impeller) {
|
|
if (!impeller_context_holder.Initialize(
|
|
settings.enable_vulkan_validation)) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
#endif // ALLOW_IMPELLER
|
|
|
|
Shell::CreateCallback<PlatformView> on_create_platform_view =
|
|
fml::MakeCopyable([impeller_context_holder = std::move(
|
|
impeller_context_holder)](Shell& shell) mutable {
|
|
return std::make_unique<TesterPlatformView>(
|
|
shell, shell.GetTaskRunners(), std::move(impeller_context_holder));
|
|
});
|
|
|
|
Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
|
|
return std::make_unique<Rasterizer>(
|
|
shell, Rasterizer::MakeGpuImageBehavior::kBitmap);
|
|
};
|
|
|
|
g_shell->reset(Shell::Create(flutter::PlatformData(), //
|
|
task_runners, //
|
|
settings, //
|
|
on_create_platform_view, //
|
|
on_create_rasterizer //
|
|
)
|
|
.release());
|
|
auto shell = g_shell->get();
|
|
|
|
if (!shell || !shell->IsSetup()) {
|
|
FML_LOG(ERROR) << "Could not set up the shell.";
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (settings.application_kernel_asset.empty()) {
|
|
FML_LOG(ERROR) << "Dart kernel file not specified.";
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
shell->GetPlatformView()->NotifyCreated();
|
|
|
|
// Initialize default testing locales. There is no platform to
|
|
// pass locales on the tester, so to retain expected locale behavior,
|
|
// we emulate it in here by passing in 'en_US' and 'zh_CN' as test locales.
|
|
const char* locale_json =
|
|
"{\"method\":\"setLocale\",\"args\":[\"en\",\"US\",\"\",\"\",\"zh\","
|
|
"\"CN\",\"\",\"\"]}";
|
|
auto locale_bytes = fml::MallocMapping::Copy(
|
|
locale_json, locale_json + std::strlen(locale_json));
|
|
fml::RefPtr<flutter::PlatformMessageResponse> response;
|
|
shell->GetPlatformView()->DispatchPlatformMessage(
|
|
std::make_unique<flutter::PlatformMessage>(
|
|
"flutter/localization", std::move(locale_bytes), response));
|
|
|
|
std::initializer_list<fml::FileMapping::Protection> protection = {
|
|
fml::FileMapping::Protection::kRead};
|
|
auto main_dart_file_mapping = std::make_unique<fml::FileMapping>(
|
|
fml::OpenFile(
|
|
fml::paths::AbsolutePath(settings.application_kernel_asset).c_str(),
|
|
false, fml::FilePermission::kRead),
|
|
protection);
|
|
|
|
auto isolate_configuration =
|
|
IsolateConfiguration::CreateForKernel(std::move(main_dart_file_mapping));
|
|
|
|
if (!isolate_configuration) {
|
|
FML_LOG(ERROR) << "Could create isolate configuration.";
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
auto asset_manager = std::make_shared<flutter::AssetManager>();
|
|
asset_manager->PushBack(std::make_unique<flutter::DirectoryAssetBundle>(
|
|
fml::Duplicate(settings.assets_dir), true));
|
|
asset_manager->PushBack(std::make_unique<flutter::DirectoryAssetBundle>(
|
|
fml::OpenDirectory(settings.assets_path.c_str(), false,
|
|
fml::FilePermission::kRead),
|
|
true));
|
|
|
|
RunConfiguration run_configuration(std::move(isolate_configuration),
|
|
std::move(asset_manager));
|
|
|
|
// The script completion task observer that will be installed on the UI thread
|
|
// that watched if the engine has any live ports.
|
|
ScriptCompletionTaskObserver completion_observer(
|
|
*shell, // a valid shell
|
|
fml::MessageLoop::GetCurrent()
|
|
.GetTaskRunner(), // the message loop to terminate
|
|
run_forever // should the exit be ignored
|
|
);
|
|
|
|
bool engine_did_run = false;
|
|
|
|
fml::AutoResetWaitableEvent latch;
|
|
auto task_observer_add = [&completion_observer]() {
|
|
fml::MessageLoop::GetCurrent().AddTaskObserver(
|
|
reinterpret_cast<intptr_t>(&completion_observer),
|
|
[&completion_observer]() { completion_observer.DidProcessTask(); });
|
|
};
|
|
|
|
auto task_observer_remove = [&completion_observer, &latch]() {
|
|
fml::MessageLoop::GetCurrent().RemoveTaskObserver(
|
|
reinterpret_cast<intptr_t>(&completion_observer));
|
|
latch.Signal();
|
|
};
|
|
|
|
shell->RunEngine(std::move(run_configuration),
|
|
[&engine_did_run, &ui_task_runner,
|
|
&task_observer_add](Engine::RunStatus run_status) mutable {
|
|
if (run_status != flutter::Engine::RunStatus::Failure) {
|
|
engine_did_run = true;
|
|
// Now that our engine is initialized we can install the
|
|
// ScriptCompletionTaskObserver
|
|
fml::TaskRunner::RunNowOrPostTask(ui_task_runner,
|
|
task_observer_add);
|
|
}
|
|
});
|
|
|
|
ConfigureShell(shell);
|
|
|
|
// Run the message loop and wait for the script to do its thing.
|
|
fml::MessageLoop::GetCurrent().Run();
|
|
|
|
// Cleanup the completion observer synchronously as it is living on the
|
|
// stack.
|
|
fml::TaskRunner::RunNowOrPostTask(ui_task_runner, task_observer_remove);
|
|
latch.Wait();
|
|
|
|
delete g_shell->release();
|
|
|
|
if (!engine_did_run) {
|
|
// If the engine itself didn't have a chance to run, there is no point in
|
|
// asking it if there was an error. Signal a failure unconditionally.
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return completion_observer.GetExitCodeForLastError();
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
#define EXPORTED __declspec(dllexport)
|
|
#else
|
|
#define EXPORTED __attribute__((visibility("default")))
|
|
#endif
|
|
|
|
extern "C" {
|
|
EXPORTED Dart_Handle LoadLibraryFromKernel(const char* path) {
|
|
std::shared_ptr<fml::FileMapping> mapping =
|
|
fml::FileMapping::CreateReadOnly(path);
|
|
if (!mapping) {
|
|
return Dart_Null();
|
|
}
|
|
return DartIsolate::LoadLibraryFromKernel(mapping);
|
|
}
|
|
|
|
EXPORTED Dart_Handle LookupEntryPoint(const char* uri, const char* name) {
|
|
if (!uri || !name) {
|
|
return Dart_Null();
|
|
}
|
|
Dart_Handle lib = Dart_LookupLibrary(Dart_NewStringFromCString(uri));
|
|
if (Dart_IsError(lib)) {
|
|
return lib;
|
|
}
|
|
return Dart_GetField(lib, Dart_NewStringFromCString(name));
|
|
}
|
|
|
|
EXPORTED void Spawn(const char* entrypoint, const char* route) {
|
|
auto shell = g_shell->get();
|
|
auto isolate = Dart_CurrentIsolate();
|
|
auto spawn_task = [shell, entrypoint = std::string(entrypoint),
|
|
route = std::string(route)]() {
|
|
auto configuration = RunConfiguration::InferFromSettings(
|
|
shell->GetSettings(), /*io_worker=*/nullptr,
|
|
/*launch_type=*/IsolateLaunchType::kExistingGroup);
|
|
configuration.SetEntrypoint(entrypoint);
|
|
|
|
Shell::CreateCallback<PlatformView> on_create_platform_view =
|
|
fml::MakeCopyable([](Shell& shell) mutable {
|
|
ImpellerVulkanContextHolder impeller_context_holder;
|
|
return std::make_unique<TesterPlatformView>(
|
|
shell, shell.GetTaskRunners(),
|
|
std::move(impeller_context_holder));
|
|
});
|
|
|
|
Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
|
|
return std::make_unique<Rasterizer>(
|
|
shell, Rasterizer::MakeGpuImageBehavior::kBitmap);
|
|
};
|
|
|
|
// Spawn a shell, and keep it running until it has no live ports, then
|
|
// delete it on the platform thread.
|
|
auto spawned_shell =
|
|
shell
|
|
->Spawn(std::move(configuration), route, on_create_platform_view,
|
|
on_create_rasterizer)
|
|
.release();
|
|
|
|
ConfigureShell(spawned_shell);
|
|
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
spawned_shell->GetTaskRunners().GetUITaskRunner(), [spawned_shell]() {
|
|
fml::MessageLoop::GetCurrent().AddTaskObserver(
|
|
reinterpret_cast<intptr_t>(spawned_shell), [spawned_shell]() {
|
|
if (spawned_shell->EngineHasLivePorts()) {
|
|
return;
|
|
}
|
|
|
|
fml::MessageLoop::GetCurrent().RemoveTaskObserver(
|
|
reinterpret_cast<intptr_t>(spawned_shell));
|
|
// Shell must be deleted on the platform task runner.
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
spawned_shell->GetTaskRunners().GetPlatformTaskRunner(),
|
|
[spawned_shell]() { delete spawned_shell; });
|
|
});
|
|
});
|
|
};
|
|
Dart_ExitIsolate();
|
|
// The global shell pointer is never deleted, short of application exit.
|
|
// This UI task runner cannot be latched because it will block spawning a new
|
|
// shell in the case where flutter_tester is running multithreaded.
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
shell->GetTaskRunners().GetPlatformTaskRunner(), spawn_task);
|
|
|
|
Dart_EnterIsolate(isolate);
|
|
}
|
|
|
|
EXPORTED void ForceShutdownIsolate() {
|
|
// Enable Isolate.exit().
|
|
FML_DCHECK(Dart_CurrentIsolate() != nullptr);
|
|
Dart_Handle isolate_lib = Dart_LookupLibrary(tonic::ToDart("dart:isolate"));
|
|
FML_CHECK(!tonic::CheckAndHandleError(isolate_lib));
|
|
Dart_Handle isolate_type = Dart_GetNonNullableType(
|
|
isolate_lib, tonic::ToDart("Isolate"), 0, nullptr);
|
|
FML_CHECK(!tonic::CheckAndHandleError(isolate_type));
|
|
Dart_Handle result =
|
|
Dart_SetField(isolate_type, tonic::ToDart("_mayExit"), Dart_True());
|
|
FML_CHECK(!tonic::CheckAndHandleError(result));
|
|
}
|
|
}
|
|
|
|
} // namespace flutter
|
|
|
|
int main(int argc, char* argv[]) {
|
|
dart::bin::SetExecutableName(argv[0]);
|
|
dart::bin::SetExecutableArguments(argc - 1, argv);
|
|
|
|
auto command_line = fml::CommandLineFromPlatformOrArgcArgv(argc, argv);
|
|
|
|
if (command_line.HasOption(flutter::FlagForSwitch(flutter::Switch::Help))) {
|
|
flutter::PrintUsage("flutter_tester");
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
auto settings = flutter::SettingsFromCommandLine(command_line);
|
|
if (!command_line.positional_args().empty()) {
|
|
// The tester may not use the switch for the main dart file path. Specifying
|
|
// it as a positional argument instead.
|
|
settings.application_kernel_asset = command_line.positional_args()[0];
|
|
}
|
|
|
|
if (settings.application_kernel_asset.empty()) {
|
|
FML_LOG(ERROR) << "Dart kernel file not specified.";
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
settings.leak_vm = false;
|
|
settings.enable_platform_isolates = true;
|
|
|
|
if (settings.icu_data_path.empty()) {
|
|
settings.icu_data_path = "icudtl.dat";
|
|
}
|
|
|
|
// The tools that read logs get confused if there is a log tag specified.
|
|
settings.log_tag = "";
|
|
|
|
settings.log_message_callback = [](const std::string& tag,
|
|
const std::string& message) {
|
|
if (!tag.empty()) {
|
|
std::cout << tag << ": ";
|
|
}
|
|
std::cout << message << std::endl;
|
|
};
|
|
|
|
settings.task_observer_add = [](intptr_t key, const fml::closure& callback) {
|
|
fml::MessageLoop::GetCurrent().AddTaskObserver(key, callback);
|
|
};
|
|
|
|
settings.task_observer_remove = [](intptr_t key) {
|
|
fml::MessageLoop::GetCurrent().RemoveTaskObserver(key);
|
|
};
|
|
|
|
settings.unhandled_exception_callback = [](const std::string& error,
|
|
const std::string& stack_trace) {
|
|
FML_LOG(ERROR) << "Unhandled exception" << std::endl
|
|
<< "Exception: " << error << std::endl
|
|
<< "Stack trace: " << stack_trace;
|
|
::exit(1);
|
|
return true;
|
|
};
|
|
|
|
#if defined(FML_OS_WIN)
|
|
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
#endif // defined(FML_OS_WIN)
|
|
|
|
return flutter::RunTester(settings,
|
|
command_line.HasOption(flutter::FlagForSwitch(
|
|
flutter::Switch::RunForever)),
|
|
command_line.HasOption(flutter::FlagForSwitch(
|
|
flutter::Switch::ForceMultithreading)));
|
|
}
|