| // 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. | |
| #include "flutter/shell/platform/linux/fl_task_runner.h" | |
| #include "flutter/shell/platform/linux/fl_engine_private.h" | |
| static constexpr int kMicrosecondsPerNanosecond = 1000; | |
| static constexpr int kMillisecondsPerMicrosecond = 1000; | |
| struct _FlTaskRunner { | |
| GObject parent_instance; | |
| FlEngine* engine; | |
| GMutex mutex; | |
| GCond cond; | |
| guint timeout_source_id; | |
| GList /*<FlTaskRunnerTask>*/* pending_tasks; | |
| gboolean blocking_main_thread; | |
| }; | |
| typedef struct _FlTaskRunnerTask { | |
| // absolute time of task (based on g_get_monotonic_time) | |
| gint64 task_time_micros; | |
| FlutterTask task; | |
| } FlTaskRunnerTask; | |
| G_DEFINE_TYPE(FlTaskRunner, fl_task_runner, G_TYPE_OBJECT) | |
| // Removes expired tasks from the task queue and executes them. | |
| // The execution is performed with mutex unlocked. | |
| static void fl_task_runner_process_expired_tasks_locked(FlTaskRunner* self) { | |
| GList* expired_tasks = nullptr; | |
| gint64 current_time = g_get_monotonic_time(); | |
| GList* l = self->pending_tasks; | |
| while (l != nullptr) { | |
| FlTaskRunnerTask* task = static_cast<FlTaskRunnerTask*>(l->data); | |
| if (task->task_time_micros <= current_time) { | |
| GList* link = l; | |
| l = l->next; | |
| self->pending_tasks = g_list_remove_link(self->pending_tasks, link); | |
| expired_tasks = g_list_concat(expired_tasks, link); | |
| } else { | |
| l = l->next; | |
| } | |
| } | |
| g_mutex_unlock(&self->mutex); | |
| l = expired_tasks; | |
| while (l != nullptr && self->engine) { | |
| FlTaskRunnerTask* task = static_cast<FlTaskRunnerTask*>(l->data); | |
| fl_engine_execute_task(self->engine, &task->task); | |
| l = l->next; | |
| } | |
| g_list_free_full(expired_tasks, g_free); | |
| g_mutex_lock(&self->mutex); | |
| } | |
| static void fl_task_runner_tasks_did_change_locked(FlTaskRunner* self); | |
| // Invoked from a timeout source. Removes and executes expired tasks | |
| // and reschedules timeout if needed. | |
| static gboolean fl_task_runner_on_expired_timeout(gpointer data) { | |
| FlTaskRunner* self = FL_TASK_RUNNER(data); | |
| g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); | |
| (void)locker; // unused variable | |
| g_object_ref(self); | |
| self->timeout_source_id = 0; | |
| fl_task_runner_process_expired_tasks_locked(self); | |
| // reschedule timeout | |
| fl_task_runner_tasks_did_change_locked(self); | |
| g_object_unref(self); | |
| return FALSE; | |
| } | |
| // Returns the absolute time of next expired task (in microseconds, based on | |
| // g_get_monotonic_time). If no task is scheduled returns G_MAXINT64. | |
| static gint64 fl_task_runner_next_task_expiration_time_locked( | |
| FlTaskRunner* self) { | |
| gint64 min_time = G_MAXINT64; | |
| GList* l = self->pending_tasks; | |
| while (l != nullptr) { | |
| FlTaskRunnerTask* task = static_cast<FlTaskRunnerTask*>(l->data); | |
| min_time = MIN(min_time, task->task_time_micros); | |
| l = l->next; | |
| } | |
| return min_time; | |
| } | |
| static void fl_task_runner_tasks_did_change_locked(FlTaskRunner* self) { | |
| if (self->blocking_main_thread) { | |
| // Wake up blocked thread | |
| g_cond_signal(&self->cond); | |
| } else { | |
| // Reschedule timeout | |
| if (self->timeout_source_id != 0) { | |
| g_source_remove(self->timeout_source_id); | |
| self->timeout_source_id = 0; | |
| } | |
| gint64 min_time = fl_task_runner_next_task_expiration_time_locked(self); | |
| if (min_time != G_MAXINT64) { | |
| gint64 remaining = MAX(min_time - g_get_monotonic_time(), 0); | |
| self->timeout_source_id = | |
| g_timeout_add(remaining / kMillisecondsPerMicrosecond + 1, | |
| fl_task_runner_on_expired_timeout, self); | |
| } | |
| } | |
| } | |
| static void engine_weak_notify_cb(gpointer user_data, | |
| GObject* where_the_object_was) { | |
| FlTaskRunner* self = FL_TASK_RUNNER(user_data); | |
| self->engine = nullptr; | |
| } | |
| void fl_task_runner_dispose(GObject* object) { | |
| FlTaskRunner* self = FL_TASK_RUNNER(object); | |
| // this should never happen because the task runner is retained while blocking | |
| // main thread | |
| g_assert(!self->blocking_main_thread); | |
| if (self->engine != nullptr) { | |
| g_object_weak_unref(G_OBJECT(self->engine), engine_weak_notify_cb, self); | |
| self->engine = nullptr; | |
| } | |
| g_mutex_clear(&self->mutex); | |
| g_cond_clear(&self->cond); | |
| g_list_free_full(self->pending_tasks, g_free); | |
| if (self->timeout_source_id != 0) { | |
| g_source_remove(self->timeout_source_id); | |
| } | |
| G_OBJECT_CLASS(fl_task_runner_parent_class)->dispose(object); | |
| } | |
| static void fl_task_runner_class_init(FlTaskRunnerClass* klass) { | |
| G_OBJECT_CLASS(klass)->dispose = fl_task_runner_dispose; | |
| } | |
| static void fl_task_runner_init(FlTaskRunner* self) { | |
| g_mutex_init(&self->mutex); | |
| g_cond_init(&self->cond); | |
| } | |
| FlTaskRunner* fl_task_runner_new(FlEngine* engine) { | |
| FlTaskRunner* res = | |
| FL_TASK_RUNNER(g_object_new(fl_task_runner_get_type(), nullptr)); | |
| res->engine = engine; | |
| g_object_weak_ref(G_OBJECT(engine), engine_weak_notify_cb, res); | |
| return res; | |
| } | |
| void fl_task_runner_post_task(FlTaskRunner* self, | |
| FlutterTask task, | |
| uint64_t target_time_nanos) { | |
| g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); | |
| (void)locker; // unused variable | |
| FlTaskRunnerTask* runner_task = g_new0(FlTaskRunnerTask, 1); | |
| runner_task->task = task; | |
| runner_task->task_time_micros = | |
| target_time_nanos / kMicrosecondsPerNanosecond; | |
| self->pending_tasks = g_list_append(self->pending_tasks, runner_task); | |
| fl_task_runner_tasks_did_change_locked(self); | |
| } | |
| void fl_task_runner_block_main_thread(FlTaskRunner* self) { | |
| g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); | |
| (void)locker; // unused variable | |
| g_return_if_fail(self->blocking_main_thread == FALSE); | |
| g_object_ref(self); | |
| self->blocking_main_thread = true; | |
| while (self->blocking_main_thread) { | |
| g_cond_wait_until(&self->cond, &self->mutex, | |
| fl_task_runner_next_task_expiration_time_locked(self)); | |
| fl_task_runner_process_expired_tasks_locked(self); | |
| } | |
| // Tasks might have changed in the meanwhile, reschedule timeout | |
| fl_task_runner_tasks_did_change_locked(self); | |
| g_object_unref(self); | |
| } | |
| void fl_task_runner_release_main_thread(FlTaskRunner* self) { | |
| g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); | |
| (void)locker; // unused variable | |
| g_return_if_fail(self->blocking_main_thread == TRUE); | |
| self->blocking_main_thread = FALSE; | |
| g_cond_signal(&self->cond); | |
| } |