stuartmorgan 716dbf00c8
Refactor GLFW embedding to support headless mode (#18205)
This does some long-overdue refactoring of the spaghetti code that grew in the GLFW embedding to begin providing a clearer separation between the engine and the window. It is now possible to register plugins, and run the runloop, on a headless engine, which makes headless mode much more usable. This is useful in some automated testing environments.

There is more refactoring that should be done in the future, but this is a good incremental point to stop as the PR is already large, and it provides useful new functionality as-is.
2020-05-07 17:10:11 -07:00

105 lines
3.5 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.
#include "flutter/shell/platform/glfw/event_loop.h"
#include <atomic>
#include <utility>
namespace flutter {
EventLoop::EventLoop(std::thread::id main_thread_id,
const TaskExpiredCallback& on_task_expired)
: main_thread_id_(main_thread_id),
on_task_expired_(std::move(on_task_expired)) {}
EventLoop::~EventLoop() = default;
bool EventLoop::RunsTasksOnCurrentThread() const {
return std::this_thread::get_id() == main_thread_id_;
}
void EventLoop::WaitForEvents(std::chrono::nanoseconds max_wait) {
const auto now = TaskTimePoint::clock::now();
std::vector<FlutterTask> expired_tasks;
// Process expired tasks.
{
std::lock_guard<std::mutex> lock(task_queue_mutex_);
while (!task_queue_.empty()) {
const auto& top = task_queue_.top();
// If this task (and all tasks after this) has not yet expired, there is
// nothing more to do. Quit iterating.
if (top.fire_time > now) {
break;
}
// Make a record of the expired task. Do NOT service the task here
// because we are still holding onto the task queue mutex. We don't want
// other threads to block on posting tasks onto this thread till we are
// done processing expired tasks.
expired_tasks.push_back(task_queue_.top().task);
// Remove the tasks from the delayed tasks queue.
task_queue_.pop();
}
}
// Fire expired tasks.
{
// Flushing tasks here without holing onto the task queue mutex.
for (const auto& task : expired_tasks) {
on_task_expired_(&task);
}
}
// Sleep till the next task needs to be processed. If a new task comes
// along, the wait will be resolved early because PostTask calls Wake().
{
TaskTimePoint next_wake;
{
std::lock_guard<std::mutex> lock(task_queue_mutex_);
TaskTimePoint max_wake_timepoint =
max_wait == std::chrono::nanoseconds::max() ? TaskTimePoint::max()
: now + max_wait;
TaskTimePoint next_event_timepoint = task_queue_.empty()
? TaskTimePoint::max()
: task_queue_.top().fire_time;
next_wake = std::min(max_wake_timepoint, next_event_timepoint);
}
WaitUntil(next_wake);
}
}
EventLoop::TaskTimePoint EventLoop::TimePointFromFlutterTime(
uint64_t flutter_target_time_nanos) {
const auto now = TaskTimePoint::clock::now();
const int64_t flutter_duration =
flutter_target_time_nanos - FlutterEngineGetCurrentTime();
return now + std::chrono::nanoseconds(flutter_duration);
}
void EventLoop::PostTask(FlutterTask flutter_task,
uint64_t flutter_target_time_nanos) {
static std::atomic_uint64_t sGlobalTaskOrder(0);
Task task;
task.order = ++sGlobalTaskOrder;
task.fire_time = TimePointFromFlutterTime(flutter_target_time_nanos);
task.task = flutter_task;
{
std::lock_guard<std::mutex> lock(task_queue_mutex_);
task_queue_.push(task);
// Make sure the queue mutex is unlocked before waking up the loop. In case
// the wake causes this thread to be descheduled for the primary thread to
// process tasks, the acquisition of the lock on that thread while holding
// the lock here momentarily till the end of the scope is a pessimization.
}
Wake();
}
} // namespace flutter