Add fl_engine_add/remove_view (flutter/engine#54018)

This is API that will be required when we get multi-view support on
Linux. While this internal API is not currently used, I've made a PR so
it can be more easily reviewed.
This commit is contained in:
Robert Ancell 2024-07-23 23:34:47 +12:00 committed by GitHub
parent d11f10ccb6
commit e1f2360083
3 changed files with 401 additions and 20 deletions

View File

@ -49,6 +49,9 @@ struct _FlEngine {
FLUTTER_API_SYMBOL(FlutterEngine) engine;
FlutterEngineProcTable embedder_api;
// Next ID to use for a view.
FlutterViewId next_view_id;
// Function to call when a platform message is received.
FlEnginePlatformMessageHandler platform_message_handler;
gpointer platform_message_handler_data;
@ -123,6 +126,29 @@ static void parse_locale(const gchar* locale,
}
}
static void view_added_cb(const FlutterAddViewResult* result) {
g_autoptr(GTask) task = G_TASK(result->user_data);
FlutterViewId view_id = GPOINTER_TO_INT(g_task_get_task_data(task));
if (result->added) {
g_task_return_pointer(task, GINT_TO_POINTER(view_id), nullptr);
} else {
g_task_return_new_error(task, fl_engine_error_quark(),
FL_ENGINE_ERROR_FAILED, "Failed to add view");
}
}
static void view_removed_cb(const FlutterRemoveViewResult* result) {
g_autoptr(GTask) task = G_TASK(result->user_data);
if (result->removed) {
g_task_return_boolean(task, TRUE);
} else {
g_task_return_new_error(task, fl_engine_error_quark(),
FL_ENGINE_ERROR_FAILED, "Failed to remove view");
}
}
// Passes locale information to the Flutter engine.
static void setup_locales(FlEngine* self) {
const gchar* const* languages = g_get_language_names();
@ -177,12 +203,11 @@ static bool compositor_collect_backing_store_callback(
}
// Called when embedder should composite contents of each layer onto the screen.
static bool compositor_present_layers_callback(const FlutterLayer** layers,
size_t layers_count,
void* user_data) {
g_return_val_if_fail(FL_IS_RENDERER(user_data), false);
return fl_renderer_present_layers(FL_RENDERER(user_data), layers,
layers_count);
static bool compositor_present_view_callback(
const FlutterPresentViewInfo* info) {
g_return_val_if_fail(FL_IS_RENDERER(info->user_data), false);
return fl_renderer_present_layers(FL_RENDERER(info->user_data), info->layers,
info->layers_count);
}
// Flutter engine rendering callbacks.
@ -211,7 +236,7 @@ static uint32_t fl_engine_gl_get_fbo(void* user_data) {
static bool fl_engine_gl_present(void* user_data) {
// No action required, as this is handled in
// compositor_present_layers_callback.
// compositor_present_view_callback.
return true;
}
@ -429,6 +454,9 @@ static void fl_engine_init(FlEngine* self) {
self->embedder_api.struct_size = sizeof(FlutterEngineProcTable);
FlutterEngineGetProcAddresses(&self->embedder_api);
// Implicit view is 0, so start at 1.
self->next_view_id = 1;
self->texture_registrar = fl_texture_registrar_new(self);
}
@ -511,7 +539,7 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
compositor_create_backing_store_callback;
compositor.collect_backing_store_callback =
compositor_collect_backing_store_callback;
compositor.present_layers_callback = compositor_present_layers_callback;
compositor.present_view_callback = compositor_present_view_callback;
args.compositor = &compositor;
if (self->embedder_api.RunsAOTCompiledDartCode()) {
@ -580,6 +608,82 @@ FlutterEngineProcTable* fl_engine_get_embedder_api(FlEngine* self) {
return &(self->embedder_api);
}
void fl_engine_add_view(FlEngine* self,
size_t width,
size_t height,
double pixel_ratio,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
g_return_if_fail(FL_IS_ENGINE(self));
g_autoptr(GTask) task = g_task_new(self, cancellable, callback, user_data);
FlutterViewId view_id = self->next_view_id;
self->next_view_id++;
g_task_set_task_data(task, GINT_TO_POINTER(view_id), nullptr);
FlutterWindowMetricsEvent metrics;
metrics.struct_size = sizeof(FlutterWindowMetricsEvent);
metrics.width = width;
metrics.height = height;
metrics.pixel_ratio = pixel_ratio;
metrics.view_id = view_id;
FlutterAddViewInfo info;
info.struct_size = sizeof(FlutterAddViewInfo);
info.view_id = view_id;
info.view_metrics = &metrics;
info.user_data = g_object_ref(task);
info.add_view_callback = view_added_cb;
FlutterEngineResult result = self->embedder_api.AddView(self->engine, &info);
if (result != kSuccess) {
g_task_return_new_error(task, fl_engine_error_quark(),
FL_ENGINE_ERROR_FAILED, "AddView returned %d",
result);
// This would have been done in the callback, but that won't occur now.
g_object_unref(task);
}
}
FlutterViewId fl_engine_add_view_finish(FlEngine* self,
GAsyncResult* result,
GError** error) {
g_return_val_if_fail(FL_IS_ENGINE(self), FALSE);
return GPOINTER_TO_INT(g_task_propagate_pointer(G_TASK(result), error));
}
void fl_engine_remove_view(FlEngine* self,
FlutterViewId view_id,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
g_return_if_fail(FL_IS_ENGINE(self));
g_autoptr(GTask) task = g_task_new(self, cancellable, callback, user_data);
FlutterRemoveViewInfo info;
info.struct_size = sizeof(FlutterRemoveViewInfo);
info.view_id = view_id;
info.user_data = g_object_ref(task);
info.remove_view_callback = view_removed_cb;
FlutterEngineResult result =
self->embedder_api.RemoveView(self->engine, &info);
if (result != kSuccess) {
g_task_return_new_error(task, fl_engine_error_quark(),
FL_ENGINE_ERROR_FAILED, "RemoveView returned %d",
result);
// This would have been done in the callback, but that won't occur now.
g_object_unref(task);
}
}
gboolean fl_engine_remove_view_finish(FlEngine* self,
GAsyncResult* result,
GError** error) {
g_return_val_if_fail(FL_IS_ENGINE(self), FALSE);
return g_task_propagate_boolean(G_TASK(result), error);
}
void fl_engine_set_platform_message_handler(
FlEngine* self,
FlEnginePlatformMessageHandler handler,

View File

@ -81,6 +81,18 @@ typedef void (*FlEngineOnPreEngineRestartHandler)(FlEngine* engine,
*/
FlEngine* fl_engine_new(FlDartProject* project, FlRenderer* renderer);
/**
* fl_engine_start:
* @engine: an #FlEngine.
* @error: (allow-none): #GError location to store the error occurring, or %NULL
* to ignore.
*
* Starts the Flutter engine.
*
* Returns: %TRUE on success.
*/
gboolean fl_engine_start(FlEngine* engine, GError** error);
/**
* fl_engine_get_embedder_api:
* @engine: an #FlEngine.
@ -91,6 +103,74 @@ FlEngine* fl_engine_new(FlDartProject* project, FlRenderer* renderer);
*/
FlutterEngineProcTable* fl_engine_get_embedder_api(FlEngine* engine);
/**
* fl_engine_add_view:
* @engine: an #FlEngine.
* @width: width of view in pixels.
* @height: height of view in pixels.
* @pixel_ratio: scale factor for view.
* @cancellable: (allow-none): a #GCancellable or %NULL.
* @callback: (scope async): a #GAsyncReadyCallback to call when the view is
* added.
* @user_data: (closure): user data to pass to @callback.
*
* Asynchronously add a new view.
*/
void fl_engine_add_view(FlEngine* engine,
size_t width,
size_t height,
double pixel_ratio,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
/**
* fl_engine_add_view_finish:
* @engine: an #FlEngine.
* @result: a #GAsyncResult.
* @error: (allow-none): #GError location to store the error occurring, or %NULL
* to ignore.
*
* Completes request started with fl_engine_add_view().
*
* Returns: the newly added view ID or 0 on error.
*/
FlutterViewId fl_engine_add_view_finish(FlEngine* engine,
GAsyncResult* result,
GError** error);
/**
* fl_engine_remove_view:
* @engine: an #FlEngine.
* @view_id: ID to remove.
* @cancellable: (allow-none): a #GCancellable or %NULL.
* @callback: (scope async): a #GAsyncReadyCallback to call when the view is
* added.
* @user_data: (closure): user data to pass to @callback.
*
* Removes a view previously added with fl_engine_add_view().
*/
void fl_engine_remove_view(FlEngine* engine,
FlutterViewId view_id,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
/**
* fl_engine_remove_view_finish:
* @engine: an #FlEngine.
* @result: a #GAsyncResult.
* @error: (allow-none): #GError location to store the error occurring, or %NULL
* to ignore.
*
* Completes request started with fl_engine_remove_view().
*
* Returns: TRUE on succcess.
*/
gboolean fl_engine_remove_view_finish(FlEngine* engine,
GAsyncResult* result,
GError** error);
/**
* fl_engine_set_platform_message_handler:
* @engine: an #FlEngine.
@ -143,18 +223,6 @@ void fl_engine_set_on_pre_engine_restart_handler(
gpointer user_data,
GDestroyNotify destroy_notify);
/**
* fl_engine_start:
* @engine: an #FlEngine.
* @error: (allow-none): #GError location to store the error occurring, or %NULL
* to ignore.
*
* Starts the Flutter engine.
*
* Returns: %TRUE on success.
*/
gboolean fl_engine_start(FlEngine* engine, GError** error);
/**
* fl_engine_send_window_metrics_event:
* @engine: an #FlEngine.

View File

@ -425,6 +425,215 @@ TEST(FlEngineTest, SwitchesEmpty) {
EXPECT_EQ(switches->len, 0U);
}
static void add_view_cb(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(GError) error = nullptr;
FlutterViewId view_id =
fl_engine_add_view_finish(FL_ENGINE(object), result, &error);
EXPECT_GT(view_id, 0);
EXPECT_EQ(error, nullptr);
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}
TEST(FlEngineTest, AddView) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
bool called = false;
embedder_api->AddView = MOCK_ENGINE_PROC(
AddView, ([&called](auto engine, const FlutterAddViewInfo* info) {
called = true;
EXPECT_EQ(info->view_metrics->width, 123u);
EXPECT_EQ(info->view_metrics->height, 456u);
EXPECT_EQ(info->view_metrics->pixel_ratio, 2.0);
FlutterAddViewResult result;
result.struct_size = sizeof(FlutterAddViewResult);
result.added = true;
result.user_data = info->user_data;
info->add_view_callback(&result);
return kSuccess;
}));
fl_engine_add_view(engine, 123, 456, 2.0, nullptr, add_view_cb, loop);
EXPECT_TRUE(called);
// Blocks here until add_view_cb is called.
g_main_loop_run(loop);
}
static void add_view_error_cb(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(GError) error = nullptr;
FlutterViewId view_id =
fl_engine_add_view_finish(FL_ENGINE(object), result, &error);
EXPECT_EQ(view_id, 0);
EXPECT_NE(error, nullptr);
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}
TEST(FlEngineTest, AddViewError) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
embedder_api->AddView = MOCK_ENGINE_PROC(
AddView, ([](auto engine, const FlutterAddViewInfo* info) {
FlutterAddViewResult result;
result.struct_size = sizeof(FlutterAddViewResult);
result.added = false;
result.user_data = info->user_data;
info->add_view_callback(&result);
return kSuccess;
}));
fl_engine_add_view(engine, 123, 456, 2.0, nullptr, add_view_error_cb, loop);
// Blocks here until add_view_error_cb is called.
g_main_loop_run(loop);
}
static void add_view_engine_error_cb(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(GError) error = nullptr;
FlutterViewId view_id =
fl_engine_add_view_finish(FL_ENGINE(object), result, &error);
EXPECT_EQ(view_id, 0);
EXPECT_NE(error, nullptr);
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}
TEST(FlEngineTest, AddViewEngineError) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
embedder_api->AddView = MOCK_ENGINE_PROC(
AddView, ([](auto engine, const FlutterAddViewInfo* info) {
return kInvalidArguments;
}));
fl_engine_add_view(engine, 123, 456, 2.0, nullptr, add_view_engine_error_cb,
loop);
// Blocks here until remove_view_engine_error_cb is called.
g_main_loop_run(loop);
}
static void remove_view_cb(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(GError) error = nullptr;
gboolean r = fl_engine_remove_view_finish(FL_ENGINE(object), result, &error);
EXPECT_TRUE(r);
EXPECT_EQ(error, nullptr);
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}
TEST(FlEngineTest, RemoveView) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
bool called = false;
embedder_api->RemoveView = MOCK_ENGINE_PROC(
RemoveView, ([&called](auto engine, const FlutterRemoveViewInfo* info) {
called = true;
EXPECT_EQ(info->view_id, 123);
FlutterRemoveViewResult result;
result.struct_size = sizeof(FlutterRemoveViewResult);
result.removed = true;
result.user_data = info->user_data;
info->remove_view_callback(&result);
return kSuccess;
}));
fl_engine_remove_view(engine, 123, nullptr, remove_view_cb, loop);
EXPECT_TRUE(called);
// Blocks here until remove_view_cb is called.
g_main_loop_run(loop);
}
static void remove_view_error_cb(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(GError) error = nullptr;
gboolean r = fl_engine_remove_view_finish(FL_ENGINE(object), result, &error);
EXPECT_FALSE(r);
EXPECT_NE(error, nullptr);
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}
TEST(FlEngineTest, RemoveViewError) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
embedder_api->RemoveView = MOCK_ENGINE_PROC(
RemoveView, ([](auto engine, const FlutterRemoveViewInfo* info) {
FlutterRemoveViewResult result;
result.struct_size = sizeof(FlutterRemoveViewResult);
result.removed = false;
result.user_data = info->user_data;
info->remove_view_callback(&result);
return kSuccess;
}));
fl_engine_remove_view(engine, 123, nullptr, remove_view_error_cb, loop);
// Blocks here until remove_view_error_cb is called.
g_main_loop_run(loop);
}
static void remove_view_engine_error_cb(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(GError) error = nullptr;
gboolean r = fl_engine_remove_view_finish(FL_ENGINE(object), result, &error);
EXPECT_FALSE(r);
EXPECT_NE(error, nullptr);
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}
TEST(FlEngineTest, RemoveViewEngineError) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
embedder_api->RemoveView = MOCK_ENGINE_PROC(
RemoveView, ([](auto engine, const FlutterRemoveViewInfo* info) {
return kInvalidArguments;
}));
fl_engine_remove_view(engine, 123, nullptr, remove_view_engine_error_cb,
loop);
// Blocks here until remove_view_engine_error_cb is called.
g_main_loop_run(loop);
}
#ifndef FLUTTER_RELEASE
TEST(FlEngineTest, Switches) {
g_autoptr(FlEngine) engine = make_mock_engine();