提交 26a6615d 编写于 作者: C Chinmay Garde 提交者: GitHub

Implementations of platform agnostic portions of FML. (#3487)

上级 f1d40b32
// Copyright 2017 The Chromium 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/fml/icu_util.h"
#include <memory>
#include <mutex>
#include "flutter/fml/mapping.h"
#include "lib/ftl/build_config.h"
#include "lib/ftl/logging.h"
#include "third_party/icu/source/common/unicode/udata.h"
namespace fml {
namespace icu {
static constexpr char kIcuDataFileName[] = "icudtl.dat";
class ICUContext {
public:
ICUContext(const std::string& icu_data_path) : valid_(false) {
valid_ = SetupMapping(icu_data_path) && SetupICU();
}
~ICUContext() = default;
bool SetupMapping(const std::string& icu_data_path) {
// Check if the explicit path specified exists.
auto overriden_path_mapping = std::make_unique<FileMapping>(icu_data_path);
if (overriden_path_mapping->GetSize() != 0) {
mapping_ = std::move(overriden_path_mapping);
return true;
}
// Check to see if the mapping is in the resources bundle.
if (PlatformHasResourcesBundle()) {
auto resource = GetResourceMapping(kIcuDataFileName);
if (resource != nullptr && resource->GetSize() != 0) {
mapping_ = std::move(resource);
return true;
}
}
// Check if the mapping can by directly accessed via a file path. In this
// case, the data file needs to be next to the executable.
auto file = std::make_unique<FileMapping>(kIcuDataFileName);
if (file->GetSize() != 0) {
mapping_ = std::move(file);
return true;
}
return false;
}
bool SetupICU() {
if (GetSize() == 0) {
return false;
}
UErrorCode err_code = U_ZERO_ERROR;
udata_setCommonData(GetMapping(), &err_code);
return (err_code == U_ZERO_ERROR);
}
const uint8_t* GetMapping() const {
return mapping_ ? mapping_->GetMapping() : nullptr;
}
size_t GetSize() const { return mapping_ ? mapping_->GetSize() : 0; }
bool IsValid() const { return valid_; }
private:
bool valid_;
std::unique_ptr<Mapping> mapping_;
FTL_DISALLOW_COPY_AND_ASSIGN(ICUContext);
};
void InitializeICUOnce(const std::string& icu_data_path) {
static ICUContext* context = new ICUContext(icu_data_path);
FTL_CHECK(context->IsValid()) << "Must be able to initialize the ICU context";
}
std::once_flag g_icu_init_flag;
void InitializeICU(const std::string& icu_data_path) {
std::call_once(g_icu_init_flag,
[&icu_data_path]() { InitializeICUOnce(icu_data_path); });
}
} // namespace icu
} // namespace fml
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_ICU_UTIL_H_
#define FLUTTER_FML_ICU_UTIL_H_
#include <string>
#include "lib/ftl/macros.h"
namespace fml {
namespace icu {
void InitializeICU(const std::string& icu_data_path = "");
} // namespace icu
} // namespace fml
#endif // FLUTTER_FML_ICU_UTIL_H_
// Copyright 2017 The Chromium 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/fml/mapping.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <type_traits>
#include "lib/ftl/build_config.h"
#include "lib/ftl/files/eintr_wrapper.h"
#if OS_MACOSX
#include "flutter/fml/platform/darwin/resource_mapping_darwin.h"
using PlatformResourceMapping = fml::ResourceMappingDarwin;
#else
using PlatformResourceMapping = fml::FileMapping;
#endif
namespace fml {
Mapping::Mapping() = default;
Mapping::~Mapping() = default;
bool PlatformHasResourcesBundle() {
return !std::is_same<PlatformResourceMapping, FileMapping>::value;
}
std::unique_ptr<Mapping> GetResourceMapping(const std::string& resource_name) {
return std::make_unique<PlatformResourceMapping>(resource_name);
}
FileMapping::FileMapping(const std::string& path)
: FileMapping(ftl::UniqueFD{HANDLE_EINTR(::open(path.c_str(), O_RDONLY))}) {
}
FileMapping::FileMapping(const ftl::UniqueFD& handle)
: size_(0), mapping_(nullptr) {
if (!handle.is_valid()) {
return;
}
struct stat stat_buffer = {};
if (::fstat(handle.get(), &stat_buffer) != 0) {
return;
}
if (stat_buffer.st_size <= 0) {
return;
}
auto mapping = ::mmap(nullptr, stat_buffer.st_size, PROT_READ, MAP_PRIVATE,
handle.get(), 0);
if (mapping == MAP_FAILED) {
return;
}
mapping_ = static_cast<uint8_t*>(mapping);
size_ = stat_buffer.st_size;
}
FileMapping::~FileMapping() {
if (mapping_ != nullptr) {
::munmap(mapping_, size_);
}
}
size_t FileMapping::GetSize() const {
return size_;
}
const uint8_t* FileMapping::GetMapping() const {
return mapping_;
}
} // namespace fml
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_MAPPING_H_
#define FLUTTER_FML_MAPPING_H_
#include <string>
#include "lib/ftl/files/unique_fd.h"
#include "lib/ftl/macros.h"
namespace fml {
class Mapping {
public:
Mapping();
virtual ~Mapping();
virtual size_t GetSize() const = 0;
virtual const uint8_t* GetMapping() const = 0;
private:
FTL_DISALLOW_COPY_AND_ASSIGN(Mapping);
};
bool PlatformHasResourcesBundle();
std::unique_ptr<Mapping> GetResourceMapping(const std::string& resource_name);
class FileMapping : public Mapping {
public:
FileMapping(const std::string& path);
FileMapping(const ftl::UniqueFD& fd);
~FileMapping() override;
size_t GetSize() const override;
const uint8_t* GetMapping() const override;
private:
size_t size_;
uint8_t* mapping_;
FTL_DISALLOW_COPY_AND_ASSIGN(FileMapping);
};
} // namespace fml
#endif // FLUTTER_FML_MAPPING_H_
// Copyright 2017 The Chromium 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/fml/message_loop.h"
#include <utility>
#include "flutter/fml/message_loop_impl.h"
#include "flutter/fml/task_runner.h"
#include "flutter/fml/thread_local.h"
#include "lib/ftl/memory/ref_counted.h"
#include "lib/ftl/memory/ref_ptr.h"
namespace fml {
FML_THREAD_LOCAL ThreadLocal tls_message_loop([](intptr_t value) {
delete reinterpret_cast<MessageLoop*>(value);
});
MessageLoop& MessageLoop::GetCurrent() {
auto loop = reinterpret_cast<MessageLoop*>(tls_message_loop.Get());
FTL_CHECK(loop != nullptr)
<< "MessageLoop::EnsureInitializedForCurrentThread was not called on "
"this thread prior to message loop use.";
return *loop;
}
void MessageLoop::EnsureInitializedForCurrentThread() {
if (tls_message_loop.Get() != 0) {
// Already initialized.
return;
}
tls_message_loop.Set(reinterpret_cast<intptr_t>(new MessageLoop()));
}
bool MessageLoop::IsInitializedForCurrentThread() {
return tls_message_loop.Get() != 0;
}
MessageLoop::MessageLoop()
: loop_(MessageLoopImpl::Create()),
task_runner_(ftl::MakeRefCounted<fml::TaskRunner>(loop_)) {
FTL_CHECK(loop_);
FTL_CHECK(task_runner_);
}
MessageLoop::~MessageLoop() = default;
void MessageLoop::Run() {
loop_->DoRun();
}
void MessageLoop::Terminate() {
loop_->DoTerminate();
}
ftl::RefPtr<ftl::TaskRunner> MessageLoop::GetTaskRunner() const {
return task_runner_;
}
ftl::RefPtr<MessageLoopImpl> MessageLoop::GetLoopImpl() const {
return loop_;
}
void MessageLoop::SetTaskObserver(TaskObserver observer) {
loop_->SetTaskObserver(std::move(observer));
}
} // namespace fml
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_MESSAGE_LOOP_H_
#define FLUTTER_FML_MESSAGE_LOOP_H_
#include "lib/ftl/macros.h"
#include "lib/ftl/tasks/task_runner.h"
namespace fml {
class TaskRunner;
class MessageLoopImpl;
class MessageLoop {
public:
static MessageLoop& GetCurrent();
bool IsValid() const;
void Run();
void Terminate();
using TaskObserver = std::function<void(void)>;
void SetTaskObserver(TaskObserver observer);
ftl::RefPtr<ftl::TaskRunner> GetTaskRunner() const;
static void EnsureInitializedForCurrentThread();
static bool IsInitializedForCurrentThread();
~MessageLoop();
private:
friend class TaskRunner;
friend class MessageLoopImpl;
ftl::RefPtr<MessageLoopImpl> loop_;
ftl::RefPtr<fml::TaskRunner> task_runner_;
MessageLoop();
ftl::RefPtr<MessageLoopImpl> GetLoopImpl() const;
FTL_DISALLOW_COPY_AND_ASSIGN(MessageLoop);
};
} // namespace fml
#endif // FLUTTER_FML_MESSAGE_LOOP_H_
// Copyright 2017 The Chromium 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/fml/message_loop_impl.h"
#include "lib/ftl/build_config.h"
#if OS_MACOSX
#include "flutter/fml/platform/darwin/message_loop_darwin.h"
using PlatformMessageLoopImpl = fml::MessageLoopDarwin;
#elif OS_ANDROID
#include "flutter/fml/platform/android/message_loop_android.h"
using PlatformMessageLoopImpl = fml::MessageLoopAndroid;
#elif OS_LINUX
#include "flutter/fml/platform/linux/message_loop_linux.h"
using PlatformMessageLoopImpl = fml::MessageLoopLinux;
#else
#error This platform does not have a message loop implementation.
#endif
namespace fml {
ftl::RefPtr<MessageLoopImpl> MessageLoopImpl::Create() {
return ftl::MakeRefCounted<::PlatformMessageLoopImpl>();
}
MessageLoopImpl::MessageLoopImpl() : order_(0), terminated_(false) {}
MessageLoopImpl::~MessageLoopImpl() = default;
void MessageLoopImpl::PostTask(ftl::Closure task, ftl::TimePoint target_time) {
FTL_DCHECK(task != nullptr);
WakeUp(RegisterTaskAndGetNextWake(task, target_time));
}
void MessageLoopImpl::RunExpiredTasksNow() {
WakeUp(RunExpiredTasksAndGetNextWake());
}
void MessageLoopImpl::SetTaskObserver(MessageLoop::TaskObserver observer) {
FTL_DCHECK(MessageLoop::GetCurrent().GetLoopImpl().get() == this)
<< "Message loop task observer must be set on the same thread as the "
"loop.";
task_observer_ = observer;
}
void MessageLoopImpl::DoRun() {
if (terminated_) {
// Message loops may be run only once.
return;
}
// Allow the implementation to do its thing.
Run();
// The message loop is shutting down. Check if there are expired tasks. This
// is the last chance for expired tasks to be serviced.
RunExpiredTasksNow();
// The loop may have been implicitly terminated. This can happen if the
// implementation supports termination via platform specific APIs or just
// error conditions. Set the terminated flag manually.
terminated_ = true;
// When the message loop is in the process of shutting down, pending tasks
// should be destructed on the message loop's thread. We have just returned
// from the implementations |Run| method which we know is on the correct
// thread. Drop all pending tasks on the floor.
ftl::MutexLocker lock(&delayed_tasks_mutex_);
delayed_tasks_ = {};
}
void MessageLoopImpl::DoTerminate() {
terminated_ = true;
Terminate();
}
ftl::TimePoint MessageLoopImpl::RegisterTaskAndGetNextWake(
ftl::Closure task,
ftl::TimePoint target_time) {
if (terminated_) {
// If the message loop has already been terminated, PostTask should destruct
// |task| synchronously within this function.
return ftl::TimePoint::Max();
}
FTL_DCHECK(task != nullptr);
ftl::MutexLocker lock(&delayed_tasks_mutex_);
delayed_tasks_.push({++order_, std::move(task), target_time});
return delayed_tasks_.top().target_time;
}
ftl::TimePoint MessageLoopImpl::RunExpiredTasksAndGetNextWake() {
std::vector<ftl::Closure> invocations;
{
ftl::MutexLocker lock(&delayed_tasks_mutex_);
if (delayed_tasks_.empty()) {
return ftl::TimePoint::Max();
}
auto now = ftl::TimePoint::Now();
while (!delayed_tasks_.empty()) {
const auto& top = delayed_tasks_.top();
if (top.target_time > now) {
break;
}
invocations.emplace_back(std::move(top.task));
delayed_tasks_.pop();
}
}
for (const auto& invocation : invocations) {
invocation();
if (task_observer_) {
task_observer_();
}
}
ftl::MutexLocker lock(&delayed_tasks_mutex_);
return delayed_tasks_.empty() ? ftl::TimePoint::Max()
: delayed_tasks_.top().target_time;
}
} // namespace fml
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_MESSAGE_LOOP_IMPL_H_
#define FLUTTER_FML_MESSAGE_LOOP_IMPL_H_
#include <atomic>
#include <deque>
#include <queue>
#include <utility>
#include "flutter/fml/message_loop.h"
#include "lib/ftl/functional/closure.h"
#include "lib/ftl/macros.h"
#include "lib/ftl/memory/ref_counted.h"
#include "lib/ftl/synchronization/mutex.h"
#include "lib/ftl/synchronization/thread_annotations.h"
#include "lib/ftl/time/time_point.h"
namespace fml {
class MessageLoopImpl : public ftl::RefCountedThreadSafe<MessageLoopImpl> {
public:
static ftl::RefPtr<MessageLoopImpl> Create();
virtual ~MessageLoopImpl();
virtual void Run() = 0;
virtual void Terminate() = 0;
virtual void WakeUp(ftl::TimePoint time_point) = 0;
void PostTask(ftl::Closure task, ftl::TimePoint target_time);
void SetTaskObserver(MessageLoop::TaskObserver observer);
void DoRun();
void DoTerminate();
protected:
MessageLoopImpl();
void RunExpiredTasksNow();
private:
struct DelayedTask {
size_t order;
ftl::Closure task;
ftl::TimePoint target_time;
DelayedTask(size_t p_order,
ftl::Closure p_task,
ftl::TimePoint p_target_time)
: order(p_order), task(std::move(p_task)), target_time(p_target_time) {}
};
struct DelayedTaskCompare {
bool operator()(const DelayedTask& a, const DelayedTask& b) {
return a.target_time == b.target_time ? a.order > b.order
: a.target_time > b.target_time;
}
};
using DelayedTaskQueue = std::
priority_queue<DelayedTask, std::deque<DelayedTask>, DelayedTaskCompare>;
MessageLoop::TaskObserver task_observer_;
ftl::Mutex delayed_tasks_mutex_;
DelayedTaskQueue delayed_tasks_ FTL_GUARDED_BY(delayed_tasks_mutex_);
size_t order_ FTL_GUARDED_BY(delayed_tasks_mutex_);
std::atomic_bool terminated_;
FTL_WARN_UNUSED_RESULT
ftl::TimePoint RegisterTaskAndGetNextWake(ftl::Closure task,
ftl::TimePoint target_time);
FTL_WARN_UNUSED_RESULT
ftl::TimePoint RunExpiredTasksAndGetNextWake();
FTL_DISALLOW_COPY_AND_ASSIGN(MessageLoopImpl);
};
} // namespace fml
#endif // FLUTTER_FML_MESSAGE_LOOP_IMPL_H_
// Copyright 2017 The Chromium 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 <thread>
#include "flutter/fml/message_loop.h"
#include "gtest/gtest.h"
#include "lib/ftl/synchronization/waitable_event.h"
#define TIME_SENSITIVE(x) TimeSensitiveTest_##x
TEST(MessageLoop, GetCurrent) {
std::thread thread([]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
ASSERT_TRUE(fml::MessageLoop::GetCurrent().GetTaskRunner());
});
thread.join();
}
TEST(MessageLoop, DifferentThreadsHaveDifferentLoops) {
fml::MessageLoop* loop1 = nullptr;
ftl::AutoResetWaitableEvent latch1;
ftl::AutoResetWaitableEvent term1;
std::thread thread1([&loop1, &latch1, &term1]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
loop1 = &fml::MessageLoop::GetCurrent();
latch1.Signal();
term1.Wait();
});
fml::MessageLoop* loop2 = nullptr;
ftl::AutoResetWaitableEvent latch2;
ftl::AutoResetWaitableEvent term2;
std::thread thread2([&loop2, &latch2, &term2]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
loop2 = &fml::MessageLoop::GetCurrent();
latch2.Signal();
term2.Wait();
});
latch1.Wait();
latch2.Wait();
ASSERT_FALSE(loop1 == loop2);
term1.Signal();
term2.Signal();
thread1.join();
thread2.join();
}
TEST(MessageLoop, CanRunAndTerminate) {
bool started = false;
bool terminated = false;
std::thread thread([&started, &terminated]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = fml::MessageLoop::GetCurrent();
ASSERT_TRUE(loop.GetTaskRunner());
loop.GetTaskRunner()->PostTask([&terminated]() {
fml::MessageLoop::GetCurrent().Terminate();
terminated = true;
});
loop.Run();
started = true;
});
thread.join();
ASSERT_TRUE(started);
ASSERT_TRUE(terminated);
}
TEST(MessageLoop, NonDelayedTasksAreRunInOrder) {
const size_t count = 100;
bool started = false;
bool terminated = false;
std::thread thread([&started, &terminated, count]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = fml::MessageLoop::GetCurrent();
size_t current = 0;
for (size_t i = 0; i < count; i++) {
loop.GetTaskRunner()->PostTask([&terminated, count, i, &current]() {
ASSERT_EQ(current, i);
current++;
if (count == i + 1) {
fml::MessageLoop::GetCurrent().Terminate();
terminated = true;
}
});
}
loop.Run();
ASSERT_EQ(current, count);
started = true;
});
thread.join();
ASSERT_TRUE(started);
ASSERT_TRUE(terminated);
}
TEST(MessageLoop, DelayedTasksAtSameTimeAreRunInOrder) {
const size_t count = 100;
bool started = false;
bool terminated = false;
std::thread thread([&started, &terminated, count]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = fml::MessageLoop::GetCurrent();
size_t current = 0;
const auto now_plus_some =
ftl::TimePoint::Now() + ftl::TimeDelta::FromMilliseconds(2);
for (size_t i = 0; i < count; i++) {
loop.GetTaskRunner()->PostTaskForTime(
[&terminated, count, i, &current]() {
ASSERT_EQ(current, i);
current++;
if (count == i + 1) {
fml::MessageLoop::GetCurrent().Terminate();
terminated = true;
}
},
now_plus_some);
}
loop.Run();
ASSERT_EQ(current, count);
started = true;
});
thread.join();
ASSERT_TRUE(started);
ASSERT_TRUE(terminated);
}
TEST(MessageLoop, CheckRunsTaskOnCurrentThread) {
ftl::RefPtr<ftl::TaskRunner> runner;
ftl::AutoResetWaitableEvent latch;
std::thread thread([&runner, &latch]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = fml::MessageLoop::GetCurrent();
runner = loop.GetTaskRunner();
latch.Signal();
ASSERT_TRUE(loop.GetTaskRunner()->RunsTasksOnCurrentThread());
});
latch.Wait();
ASSERT_TRUE(runner);
ASSERT_FALSE(runner->RunsTasksOnCurrentThread());
thread.join();
}
TEST(MessageLoop, TIME_SENSITIVE(SingleDelayedTaskByDelta)) {
bool checked = false;
std::thread thread([&checked]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = fml::MessageLoop::GetCurrent();
auto begin = ftl::TimePoint::Now();
loop.GetTaskRunner()->PostDelayedTask(
[begin, &checked]() {
auto delta = ftl::TimePoint::Now() - begin;
auto ms = delta.ToMillisecondsF();
ASSERT_GE(ms, 3);
ASSERT_LE(ms, 7);
checked = true;
fml::MessageLoop::GetCurrent().Terminate();
},
ftl::TimeDelta::FromMilliseconds(5));
loop.Run();
});
thread.join();
ASSERT_TRUE(checked);
}
TEST(MessageLoop, TIME_SENSITIVE(SingleDelayedTaskForTime)) {
bool checked = false;
std::thread thread([&checked]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = fml::MessageLoop::GetCurrent();
auto begin = ftl::TimePoint::Now();
loop.GetTaskRunner()->PostTaskForTime(
[begin, &checked]() {
auto delta = ftl::TimePoint::Now() - begin;
auto ms = delta.ToMillisecondsF();
ASSERT_GE(ms, 3);
ASSERT_LE(ms, 7);
checked = true;
fml::MessageLoop::GetCurrent().Terminate();
},
ftl::TimePoint::Now() + ftl::TimeDelta::FromMilliseconds(5));
loop.Run();
});
thread.join();
ASSERT_TRUE(checked);
}
TEST(MessageLoop, TIME_SENSITIVE(MultipleDelayedTasksWithIncreasingDeltas)) {
const auto count = 10;
int checked = false;
std::thread thread([&checked, count]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = fml::MessageLoop::GetCurrent();
for (int target_ms = 0 + 2; target_ms < count + 2; target_ms++) {
auto begin = ftl::TimePoint::Now();
loop.GetTaskRunner()->PostDelayedTask(
[begin, target_ms, &checked]() {
auto delta = ftl::TimePoint::Now() - begin;
auto ms = delta.ToMillisecondsF();
ASSERT_GE(ms, target_ms - 2);
ASSERT_LE(ms, target_ms + 2);
checked++;
if (checked == count) {
fml::MessageLoop::GetCurrent().Terminate();
}
},
ftl::TimeDelta::FromMilliseconds(target_ms));
}
loop.Run();
});
thread.join();
ASSERT_EQ(checked, count);
}
TEST(MessageLoop, TIME_SENSITIVE(MultipleDelayedTasksWithDecreasingDeltas)) {
const auto count = 10;
int checked = false;
std::thread thread([&checked, count]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = fml::MessageLoop::GetCurrent();
for (int target_ms = count + 2; target_ms >= 0 + 2; target_ms--) {
auto begin = ftl::TimePoint::Now();
loop.GetTaskRunner()->PostDelayedTask(
[begin, target_ms, &checked]() {
auto delta = ftl::TimePoint::Now() - begin;
auto ms = delta.ToMillisecondsF();
ASSERT_GE(ms, target_ms - 2);
ASSERT_LE(ms, target_ms + 2);
checked++;
if (checked == count) {
fml::MessageLoop::GetCurrent().Terminate();
}
},
ftl::TimeDelta::FromMilliseconds(target_ms));
}
loop.Run();
});
thread.join();
ASSERT_EQ(checked, count);
}
TEST(MessageLoop, TaskObserverFire) {
bool started = false;
bool terminated = false;
std::thread thread([&started, &terminated]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
const size_t count = 25;
auto& loop = fml::MessageLoop::GetCurrent();
size_t task_count = 0;
size_t obs_count = 0;
loop.SetTaskObserver([&obs_count]() { obs_count++; });
for (size_t i = 0; i < count; i++) {
loop.GetTaskRunner()->PostTask([&terminated, count, i, &task_count]() {
ASSERT_EQ(task_count, i);
task_count++;
if (count == i + 1) {
fml::MessageLoop::GetCurrent().Terminate();
terminated = true;
}
});
}
loop.Run();
ASSERT_EQ(task_count, count);
ASSERT_EQ(obs_count, count);
started = true;
});
thread.join();
ASSERT_TRUE(started);
ASSERT_TRUE(terminated);
}
// Copyright 2017 The Chromium 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/fml/task_runner.h"
#include <utility>
#include "flutter/fml/message_loop.h"
#include "flutter/fml/message_loop_impl.h"
namespace fml {
TaskRunner::TaskRunner(ftl::RefPtr<MessageLoopImpl> loop)
: loop_(std::move(loop)) {
FTL_CHECK(loop_);
}
TaskRunner::~TaskRunner() = default;
void TaskRunner::PostTask(ftl::Closure task) {
loop_->PostTask(std::move(task), ftl::TimePoint::Now());
}
void TaskRunner::PostTaskForTime(ftl::Closure task,
ftl::TimePoint target_time) {
loop_->PostTask(std::move(task), target_time);
}
void TaskRunner::PostDelayedTask(ftl::Closure task, ftl::TimeDelta delay) {
loop_->PostTask(std::move(task), ftl::TimePoint::Now() + delay);
}
bool TaskRunner::RunsTasksOnCurrentThread() {
if (!fml::MessageLoop::IsInitializedForCurrentThread()) {
return false;
}
return MessageLoop::GetCurrent().GetLoopImpl() == loop_;
}
} // namespace fml
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_TASK_RUNNER_H_
#define FLUTTER_FML_TASK_RUNNER_H_
#include "lib/ftl/macros.h"
#include "lib/ftl/memory/ref_counted.h"
#include "lib/ftl/tasks/task_runner.h"
namespace fml {
class MessageLoopImpl;
class TaskRunner : public ftl::TaskRunner {
public:
void PostTask(ftl::Closure task) override;
void PostTaskForTime(ftl::Closure task, ftl::TimePoint target_time) override;
void PostDelayedTask(ftl::Closure task, ftl::TimeDelta delay) override;
bool RunsTasksOnCurrentThread() override;
private:
ftl::RefPtr<MessageLoopImpl> loop_;
TaskRunner(ftl::RefPtr<MessageLoopImpl> loop);
~TaskRunner();
FRIEND_MAKE_REF_COUNTED(TaskRunner);
FRIEND_REF_COUNTED_THREAD_SAFE(TaskRunner);
FTL_DISALLOW_COPY_AND_ASSIGN(TaskRunner);
};
} // namespace fml
#endif // FLUTTER_FML_TASK_RUNNER_H_
// Copyright 2017 The Chromium 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/fml/thread.h"
#include <memory>
#include <string>
#include "flutter/fml/message_loop.h"
#include "lib/ftl/build_config.h"
#include "lib/ftl/synchronization/waitable_event.h"
#if OS_MACOSX
#include <pthread/pthread.h>
#elif OS_LINUX || OS_ANDROID
#include <pthread.h>
#else
#error Unsupported Platform
#endif
namespace fml {
Thread::Thread(const std::string& name) : joined_(false) {
ftl::AutoResetWaitableEvent latch;
ftl::RefPtr<ftl::TaskRunner> runner;
thread_ = std::make_unique<std::thread>([&latch, &runner, name]() -> void {
SetCurrentThreadName(name);
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = MessageLoop::GetCurrent();
runner = loop.GetTaskRunner();
latch.Signal();
loop.Run();
});
latch.Wait();
task_runner_ = runner;
}
Thread::~Thread() {
Join();
}
ftl::RefPtr<ftl::TaskRunner> Thread::GetTaskRunner() const {
return task_runner_;
}
void Thread::Join() {
if (joined_) {
return;
}
joined_ = true;
task_runner_->PostTask([]() { MessageLoop::GetCurrent().Terminate(); });
thread_->join();
}
void Thread::SetCurrentThreadName(const std::string& name) {
if (name == "") {
return;
}
#if OS_MACOSX
pthread_setname_np(name.c_str());
#elif OS_LINUX || OS_ANDROID
pthread_setname_np(pthread_self(), name.c_str());
#else
#error Unsupported Platform
#endif
}
} // namespace fml
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_THREAD_H_
#define FLUTTER_FML_THREAD_H_
#include <atomic>
#include <memory>
#include <thread>
#include "lib/ftl/macros.h"
#include "lib/ftl/tasks/task_runner.h"
namespace fml {
class Thread {
public:
explicit Thread(const std::string& name = "");
~Thread();
ftl::RefPtr<ftl::TaskRunner> GetTaskRunner() const;
void Join();
private:
std::unique_ptr<std::thread> thread_;
ftl::RefPtr<ftl::TaskRunner> task_runner_;
std::atomic_bool joined_;
static void SetCurrentThreadName(const std::string& name);
FTL_DISALLOW_COPY_AND_ASSIGN(Thread);
};
} // namespace fml
#endif // FLUTTER_FML_THREAD_H_
// Copyright 2017 The Chromium 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/fml/thread_checker.h"
namespace fml {
ThreadChecker::ThreadChecker() : handle_(std::this_thread::get_id()) {}
ThreadChecker::~ThreadChecker() = default;
bool ThreadChecker::IsCalledOnValidThread() const {
return handle_ == std::this_thread::get_id();
}
} // namespace fml
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_THREAD_CHECKER_H_
#define FLUTTER_FML_THREAD_CHECKER_H_
#include <thread>
#include "lib/ftl/macros.h"
namespace fml {
class ThreadChecker {
public:
ThreadChecker();
~ThreadChecker();
bool IsCalledOnValidThread() const;
private:
const std::thread::id handle_;
FTL_DISALLOW_COPY_AND_ASSIGN(ThreadChecker);
};
} // namespace fml
#endif // FLUTTER_FML_THREAD_CHECKER_H_
// Copyright 2017 The Chromium 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 <thread>
#include "flutter/fml/thread_checker.h"
#include "gtest/gtest.h"
TEST(ThreadChecker, CheckCalledOnValidThread) {
fml::ThreadChecker checker;
ASSERT_TRUE(checker.IsCalledOnValidThread());
std::thread thread(
[&checker]() { ASSERT_FALSE(checker.IsCalledOnValidThread()); });
thread.join();
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_THREAD_LOCAL_H_
#define FLUTTER_FML_THREAD_LOCAL_H_
#include <functional>
#include "lib/ftl/build_config.h"
#include "lib/ftl/logging.h"
#include "lib/ftl/macros.h"
#define FML_THREAD_LOCAL_PTHREADS OS_MACOSX || OS_LINUX || OS_ANDROID
#if FML_THREAD_LOCAL_PTHREADS
#include <pthread.h>
#endif
namespace fml {
using ThreadLocalDestroyCallback = std::function<void(intptr_t)>;
#if FML_THREAD_LOCAL_PTHREADS
// thread_local is unavailable and we have to resort to pthreads.
#define FML_THREAD_LOCAL static
class ThreadLocal {
private:
class Box {
public:
Box(ThreadLocalDestroyCallback destroy, intptr_t value)
: destroy_(destroy), value_(value) {}
intptr_t Value() const { return value_; }
void SetValue(intptr_t value) {
if (value == value_) {
return;
}
DestroyValue();
value_ = value;
}
void DestroyValue() {
if (destroy_) {
destroy_(value_);
}
}
private:
ThreadLocalDestroyCallback destroy_;
intptr_t value_;
FTL_DISALLOW_COPY_AND_ASSIGN(Box);
};
static inline void ThreadLocalDestroy(void* value) {
FTL_CHECK(value != nullptr);
auto box = reinterpret_cast<Box*>(value);
box->DestroyValue();
delete box;
}
public:
ThreadLocal() : ThreadLocal(nullptr) {}
ThreadLocal(ThreadLocalDestroyCallback destroy) : destroy_(destroy) {
auto callback =
reinterpret_cast<void (*)(void*)>(&ThreadLocal::ThreadLocalDestroy);
FTL_CHECK(pthread_key_create(&_key, callback) == 0);
}
void Set(intptr_t value) {
auto box = reinterpret_cast<Box*>(pthread_getspecific(_key));
if (box == nullptr) {
box = new Box(destroy_, value);
FTL_CHECK(pthread_setspecific(_key, box) == 0);
} else {
box->SetValue(value);
}
}
intptr_t Get() {
auto box = reinterpret_cast<Box*>(pthread_getspecific(_key));
return box != nullptr ? box->Value() : 0;
}
~ThreadLocal() {
// This will NOT call the destroy callbacks on thread local values still
// active in other threads. Those must be cleared manually. The usage
// of this class should be similar to the thread_local keyword but with
// with a static storage specifier
// Collect the container
delete reinterpret_cast<Box*>(pthread_getspecific(_key));
// Finally, collect the key
FTL_CHECK(pthread_key_delete(_key) == 0);
}
private:
pthread_key_t _key;
ThreadLocalDestroyCallback destroy_;
FTL_DISALLOW_COPY_AND_ASSIGN(ThreadLocal);
};
#else // FML_THREAD_LOCAL_PTHREADS
#define FML_THREAD_LOCAL thread_local
class ThreadLocal {
public:
ThreadLocal() : ThreadLocal(nullptr) {}
ThreadLocal(ThreadLocalDestroyCallback destroy)
: destroy_(destroy), value_(0) {}
void Set(intptr_t value) {
if (value_ == value) {
return;
}
if (value_ != 0 && destroy_) {
destroy_(value_);
}
value_ = value;
}
intptr_t Get() { return value_; }
~ThreadLocal() {
if (value_ != 0 && destroy_) {
destroy_(value_);
}
}
private:
ThreadLocalDestroyCallback destroy_;
intptr_t value_;
FTL_DISALLOW_COPY_AND_ASSIGN(ThreadLocal);
};
#endif // FML_THREAD_LOCAL_PTHREADS
#ifndef FML_THREAD_LOCAL
#error Thread local storage unavailable on the platform.
#endif // FML_THREAD_LOCAL
} // namespace fml
#endif // FLUTTER_FML_THREAD_LOCAL_H_
// Copyright 2017 The Chromium 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 <thread>
#include "flutter/fml/thread_local.h"
#include "gtest/gtest.h"
// We are only going to test the pthreads based thread local boxes.
#if FML_THREAD_LOCAL_PTHREADS
TEST(ThreadLocal, SimpleInitialization) {
std::thread thread([&] {
fml::ThreadLocal local;
auto value = 100;
local.Set(value);
ASSERT_EQ(local.Get(), value);
});
thread.join();
}
TEST(ThreadLocal, SimpleInitializationCheckInAnother) {
std::thread thread([&] {
fml::ThreadLocal local;
auto value = 100;
local.Set(value);
ASSERT_EQ(local.Get(), value);
std::thread thread2([&]() { ASSERT_EQ(local.Get(), 0); });
thread2.join();
});
thread.join();
}
TEST(ThreadLocal, DestroyCallback) {
std::thread thread([&] {
int destroys = 0;
fml::ThreadLocal local([&destroys](intptr_t) { destroys++; });
auto value = 100;
local.Set(value);
ASSERT_EQ(local.Get(), value);
ASSERT_EQ(destroys, 0);
});
thread.join();
}
TEST(ThreadLocal, DestroyCallback2) {
std::thread thread([&] {
int destroys = 0;
fml::ThreadLocal local([&destroys](intptr_t) { destroys++; });
local.Set(100);
ASSERT_EQ(local.Get(), 100);
ASSERT_EQ(destroys, 0);
local.Set(200);
ASSERT_EQ(local.Get(), 200);
ASSERT_EQ(destroys, 1);
});
thread.join();
}
TEST(ThreadLocal, DestroyThreadTimeline) {
std::thread thread([&] {
int destroys = 0;
fml::ThreadLocal local([&destroys](intptr_t) { destroys++; });
std::thread thread([&]() {
local.Set(100);
ASSERT_EQ(local.Get(), 100);
ASSERT_EQ(destroys, 0);
local.Set(200);
ASSERT_EQ(local.Get(), 200);
ASSERT_EQ(destroys, 1);
});
ASSERT_EQ(local.Get(), 0);
thread.join();
ASSERT_EQ(local.Get(), 0);
ASSERT_EQ(destroys, 2);
});
thread.join();
}
TEST(ThreadLocal, SettingSameValue) {
std::thread thread([&] {
int destroys = 0;
{
fml::ThreadLocal local([&destroys](intptr_t) { destroys++; });
local.Set(100);
ASSERT_EQ(destroys, 0);
local.Set(100);
local.Set(100);
local.Set(100);
ASSERT_EQ(local.Get(), 100);
local.Set(100);
local.Set(100);
ASSERT_EQ(destroys, 0);
local.Set(200);
ASSERT_EQ(destroys, 1);
ASSERT_EQ(local.Get(), 200);
}
ASSERT_EQ(destroys, 1);
});
thread.join();
}
#endif // FML_THREAD_LOCAL_PTHREADS
// Copyright 2017 The Chromium 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 "gtest/gtest.h"
#include "flutter/fml/thread.h"
TEST(Thread, CanStartAndEnd) {
fml::Thread thread;
ASSERT_TRUE(thread.GetTaskRunner());
}
TEST(Thread, CanStartAndEndWithExplicitJoin) {
fml::Thread thread;
ASSERT_TRUE(thread.GetTaskRunner());
thread.Join();
}
TEST(Thread, HasARunningMessageLoop) {
fml::Thread thread;
bool done = false;
thread.GetTaskRunner()->PostTask([&done]() { done = true; });
thread.Join();
ASSERT_TRUE(done);
}
// Copyright 2017 The Chromium 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/fml/trace_event.h"
#include "dart/runtime/include/dart_tools_api.h"
namespace fml {
namespace tracing {
void TraceEvent0(TraceArg category_group, TraceArg name) {
Dart_TimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
0, // timestamp1_or_async_id
Dart_Timeline_Event_Begin, // event type
0, // argument_count
nullptr, // argument_names
nullptr // argument_values
);
}
void TraceEvent1(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val) {
const char* arg_names[] = {arg1_name};
const char* arg_values[] = {arg1_val};
Dart_TimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
0, // timestamp1_or_async_id
Dart_Timeline_Event_Begin, // event type
1, // argument_count
arg_names, // argument_names
arg_values // argument_values
);
}
void TraceEvent2(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val,
TraceArg arg2_name,
TraceArg arg2_val) {
const char* arg_names[] = {arg1_name, arg2_name};
const char* arg_values[] = {arg1_val, arg2_val};
Dart_TimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
0, // timestamp1_or_async_id
Dart_Timeline_Event_Begin, // event type
2, // argument_count
arg_names, // argument_names
arg_values // argument_values
);
}
void TraceEventEnd(TraceArg name) {
Dart_TimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
0, // timestamp1_or_async_id
Dart_Timeline_Event_End, // event type
0, // argument_count
nullptr, // argument_names
nullptr // argument_values
);
}
void TraceEventAsyncBegin0(TraceArg category_group,
TraceArg name,
TraceIDArg id) {
Dart_TimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
id, // timestamp1_or_async_id
Dart_Timeline_Event_Async_Begin, // event type
0, // argument_count
nullptr, // argument_names
nullptr // argument_values
);
}
void TraceEventAsyncEnd0(TraceArg category_group,
TraceArg name,
TraceIDArg id) {
Dart_TimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
id, // timestamp1_or_async_id
Dart_Timeline_Event_Async_End, // event type
0, // argument_count
nullptr, // argument_names
nullptr // argument_values
);
}
void TraceEventAsyncBegin1(TraceArg category_group,
TraceArg name,
TraceIDArg id,
TraceArg arg1_name,
TraceArg arg1_val) {
const char* arg_names[] = {arg1_name};
const char* arg_values[] = {arg1_val};
Dart_TimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
id, // timestamp1_or_async_id
Dart_Timeline_Event_Async_Begin, // event type
1, // argument_count
arg_names, // argument_names
arg_values // argument_values
);
}
void TraceEventAsyncEnd1(TraceArg category_group,
TraceArg name,
TraceIDArg id,
TraceArg arg1_name,
TraceArg arg1_val) {
const char* arg_names[] = {arg1_name};
const char* arg_values[] = {arg1_val};
Dart_TimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
id, // timestamp1_or_async_id
Dart_Timeline_Event_Async_End, // event type
1, // argument_count
arg_names, // argument_names
arg_values // argument_values
);
}
void TraceEventInstant0(TraceArg category_group, TraceArg name) {
Dart_TimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
0, // timestamp1_or_async_id
Dart_Timeline_Event_Instant, // event type
0, // argument_count
nullptr, // argument_names
nullptr // argument_values
);
}
} // namespace tracing
} // namespace fml
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_TRACE_EVENT_H_
#define FLUTTER_FML_TRACE_EVENT_H_
#include <cstddef>
#include <cstdint>
#include <string>
#include "lib/ftl/macros.h"
#define TRACE_EVENT0(category_group, name) \
::fml::tracing::TraceEvent0(category_group, name); \
::fml::tracing::ScopedInstantEnd __trace_end0_##__LINE__(name);
#define TRACE_EVENT1(category_group, name, arg1_name, arg1_val) \
::fml::tracing::TraceEvent1(category_group, name, arg1_name, arg1_val); \
::fml::tracing::ScopedInstantEnd __trace_end1_##__LINE__(name);
#define TRACE_EVENT2(category_group, name, arg1_name, arg1_val, arg2_name, \
arg2_val) \
::fml::tracing::TraceEvent2(category_group, name, arg1_name, arg1_val, \
arg2_name, arg2_val); \
::fml::tracing::ScopedInstantEnd __trace_end2_##__LINE__(name);
#define TRACE_EVENT_ASYNC_BEGIN0(category_group, name, id) \
::fml::tracing::TraceEventAsyncBegin0(category_group, name, id);
#define TRACE_EVENT_ASYNC_END0(category_group, name, id) \
::fml::tracing::TraceEventAsyncEnd0(category_group, name, id);
#define TRACE_EVENT_ASYNC_BEGIN1(category_group, name, id, arg1_name, \
arg1_val) \
::fml::tracing::TraceEventAsyncBegin1(category_group, name, id, arg1_name, \
arg1_val);
#define TRACE_EVENT_ASYNC_END1(category_group, name, id, arg1_name, arg1_val) \
::fml::tracing::TraceEventAsyncEnd1(category_group, name, id, arg1_name, \
arg1_val);
#define TRACE_EVENT_INSTANT0(category_group, name) \
::fml::tracing::TraceEventInstant0(category_group, name);
namespace fml {
namespace tracing {
using TraceArg = const char*;
using TraceIDArg = int64_t;
void TraceEvent0(TraceArg category_group, TraceArg name);
void TraceEvent1(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val);
void TraceEvent2(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val,
TraceArg arg2_name,
TraceArg arg2_val);
void TraceEventEnd(TraceArg name);
void TraceEventAsyncBegin0(TraceArg category_group,
TraceArg name,
TraceIDArg id);
void TraceEventAsyncEnd0(TraceArg category_group, TraceArg name, TraceIDArg id);
void TraceEventAsyncBegin1(TraceArg category_group,
TraceArg name,
TraceIDArg id,
TraceArg arg1_name,
TraceArg arg1_val);
void TraceEventAsyncEnd1(TraceArg category_group,
TraceArg name,
TraceIDArg id,
TraceArg arg1_name,
TraceArg arg1_val);
void TraceEventInstant0(TraceArg category_group, TraceArg name);
class ScopedInstantEnd {
public:
ScopedInstantEnd(std::string str) : label_(std::move(str)) {}
~ScopedInstantEnd() { TraceEventEnd(label_.c_str()); }
private:
const std::string label_;
FTL_DISALLOW_COPY_AND_ASSIGN(ScopedInstantEnd);
};
} // namespace tracing
} // namespace fml
#endif // FLUTTER_FML_TRACE_EVENT_H_
...@@ -1906,8 +1906,29 @@ FILE: ../../../flutter/content_handler/vulkan_rasterizer.cc ...@@ -1906,8 +1906,29 @@ FILE: ../../../flutter/content_handler/vulkan_rasterizer.cc
FILE: ../../../flutter/content_handler/vulkan_rasterizer.h FILE: ../../../flutter/content_handler/vulkan_rasterizer.h
FILE: ../../../flutter/flow/layers/physical_model_layer.cc FILE: ../../../flutter/flow/layers/physical_model_layer.cc
FILE: ../../../flutter/flow/layers/physical_model_layer.h FILE: ../../../flutter/flow/layers/physical_model_layer.h
FILE: ../../../flutter/fml/icu_util.cc
FILE: ../../../flutter/fml/icu_util.h
FILE: ../../../flutter/fml/mapping.cc
FILE: ../../../flutter/fml/mapping.h
FILE: ../../../flutter/fml/message_loop.cc
FILE: ../../../flutter/fml/message_loop.h
FILE: ../../../flutter/fml/message_loop_impl.cc
FILE: ../../../flutter/fml/message_loop_impl.h
FILE: ../../../flutter/fml/message_loop_unittests.cc
FILE: ../../../flutter/fml/platform/darwin/cf_utils.cc FILE: ../../../flutter/fml/platform/darwin/cf_utils.cc
FILE: ../../../flutter/fml/platform/darwin/cf_utils.h FILE: ../../../flutter/fml/platform/darwin/cf_utils.h
FILE: ../../../flutter/fml/task_runner.cc
FILE: ../../../flutter/fml/task_runner.h
FILE: ../../../flutter/fml/thread.cc
FILE: ../../../flutter/fml/thread.h
FILE: ../../../flutter/fml/thread_checker.cc
FILE: ../../../flutter/fml/thread_checker.h
FILE: ../../../flutter/fml/thread_checker_unittests.cc
FILE: ../../../flutter/fml/thread_local.h
FILE: ../../../flutter/fml/thread_local_unittests.cc
FILE: ../../../flutter/fml/thread_unittests.cc
FILE: ../../../flutter/fml/trace_event.cc
FILE: ../../../flutter/fml/trace_event.h
FILE: ../../../flutter/shell/gpu/gpu_surface_software.cc FILE: ../../../flutter/shell/gpu/gpu_surface_software.cc
FILE: ../../../flutter/shell/gpu/gpu_surface_software.h FILE: ../../../flutter/shell/gpu/gpu_surface_software.h
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BinaryCodec.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BinaryCodec.java
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册