mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-01 04:14:43 +08:00
Add a task system.
This commit is contained in:
@@ -148,15 +148,16 @@ add_library(component
|
||||
src/ftxui/component/terminal_input_parser.hpp
|
||||
src/ftxui/component/util.cpp
|
||||
src/ftxui/component/window.cpp
|
||||
src/ftxui/core/task.cpp
|
||||
src/ftxui/core/task.hpp
|
||||
src/ftxui/core/task_queue.cpp
|
||||
src/ftxui/core/task_queue.hpp
|
||||
src/ftxui/core/task_runner.cpp
|
||||
src/ftxui/core/task_runner.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(dom
|
||||
PUBLIC screen
|
||||
)
|
||||
|
||||
target_link_libraries(component
|
||||
PUBLIC dom
|
||||
)
|
||||
target_link_libraries(dom PUBLIC screen)
|
||||
target_link_libraries(component PUBLIC dom)
|
||||
|
||||
if (NOT EMSCRIPTEN)
|
||||
find_package(Threads)
|
||||
|
@@ -19,13 +19,13 @@ add_executable(ftxui-tests
|
||||
src/ftxui/component/menu_test.cpp
|
||||
src/ftxui/component/modal_test.cpp
|
||||
src/ftxui/component/radiobox_test.cpp
|
||||
src/ftxui/util/ref_test.cpp
|
||||
src/ftxui/component/receiver_test.cpp
|
||||
src/ftxui/component/resizable_split_test.cpp
|
||||
src/ftxui/component/screen_interactive_test.cpp
|
||||
src/ftxui/component/slider_test.cpp
|
||||
src/ftxui/component/terminal_input_parser_test.cpp
|
||||
src/ftxui/component/toggle_test.cpp
|
||||
src/ftxui/core/task_test.cpp
|
||||
src/ftxui/dom/blink_test.cpp
|
||||
src/ftxui/dom/bold_test.cpp
|
||||
src/ftxui/dom/border_test.cpp
|
||||
@@ -51,6 +51,7 @@ add_executable(ftxui-tests
|
||||
src/ftxui/dom/vbox_test.cpp
|
||||
src/ftxui/screen/color_test.cpp
|
||||
src/ftxui/screen/string_test.cpp
|
||||
src/ftxui/util/ref_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(ftxui-tests
|
||||
|
20
src/ftxui/core/task.cpp
Normal file
20
src/ftxui/core/task.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
#include "task.hpp"
|
||||
|
||||
namespace ftxui::task {
|
||||
bool PendingTask::operator<(const PendingTask& other) const {
|
||||
if (!time && !other.time) {
|
||||
return false;
|
||||
}
|
||||
if (!time) {
|
||||
return true;
|
||||
}
|
||||
if (!other.time) {
|
||||
return false;
|
||||
}
|
||||
return time.value() > other.time.value();
|
||||
}
|
||||
} // namespace ftxui::task
|
||||
|
42
src/ftxui/core/task.hpp
Normal file
42
src/ftxui/core/task.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2024 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
#ifndef TASK_HPP
|
||||
#define TASK_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
namespace ftxui::task {
|
||||
|
||||
/// A task represents a unit of work.
|
||||
using Task = std::function<void()>;
|
||||
|
||||
/// A PendingTask represents a task that is scheduled to be executed at a
|
||||
/// specific time, or as soon as possible.
|
||||
struct PendingTask {
|
||||
// Immediate task:
|
||||
PendingTask(Task task) : task(std::move(task)) {} // NOLINT
|
||||
|
||||
// Delayed task with a duration
|
||||
PendingTask(Task task, std::chrono::steady_clock::duration duration)
|
||||
: task(std::move(task)),
|
||||
time(std::chrono::steady_clock::now() + duration) {}
|
||||
|
||||
/// The task to be executed.
|
||||
Task task;
|
||||
|
||||
/// The time when the task should be executed. If the time is empty, the task
|
||||
/// should be executed as soon as possible.
|
||||
std::optional<std::chrono::steady_clock::time_point> time;
|
||||
|
||||
/// Compare two PendingTasks by their time.
|
||||
/// If both tasks have no time, they are considered equal.
|
||||
bool operator<(const PendingTask& other) const;
|
||||
};
|
||||
|
||||
} // namespace ftxui::task
|
||||
|
||||
|
||||
#endif // TASK_HPP_
|
54
src/ftxui/core/task_queue.cpp
Normal file
54
src/ftxui/core/task_queue.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2024 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
#include "task_queue.hpp"
|
||||
|
||||
namespace ftxui::task {
|
||||
|
||||
auto TaskQueue::PostTask(PendingTask task) -> void {
|
||||
if (!task.time) {
|
||||
immediate_tasks_.push(task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (task.time.value() < std::chrono::steady_clock::now()) {
|
||||
immediate_tasks_.push(task);
|
||||
return;
|
||||
}
|
||||
|
||||
delayed_tasks_.push(task);
|
||||
}
|
||||
|
||||
auto TaskQueue::Get() -> MaybeTask {
|
||||
// Attempt to execute a task immediately.
|
||||
if (!immediate_tasks_.empty()) {
|
||||
auto task = immediate_tasks_.front();
|
||||
immediate_tasks_.pop();
|
||||
return task.task;
|
||||
}
|
||||
|
||||
// Move all tasks that can be executed to the immediate queue.
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
while (!delayed_tasks_.empty() && delayed_tasks_.top().time.value() <= now) {
|
||||
immediate_tasks_.push(delayed_tasks_.top());
|
||||
delayed_tasks_.pop();
|
||||
}
|
||||
|
||||
// Attempt to execute a task immediately.
|
||||
if (!immediate_tasks_.empty()) {
|
||||
auto task = immediate_tasks_.front();
|
||||
immediate_tasks_.pop();
|
||||
return task.task;
|
||||
}
|
||||
|
||||
// If there are no tasks to execute, return the delay until the next task.
|
||||
if (!delayed_tasks_.empty()) {
|
||||
return delayed_tasks_.top().time.value() - now;
|
||||
}
|
||||
|
||||
// If there are no tasks to execute, return the maximum duration.
|
||||
return std::monostate{};
|
||||
}
|
||||
|
||||
} // namespace ftxui::task
|
||||
|
36
src/ftxui/core/task_queue.hpp
Normal file
36
src/ftxui/core/task_queue.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2024 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
#ifndef TASK_QUEUE_HPP
|
||||
#define TASK_QUEUE_HPP
|
||||
|
||||
#include <queue>
|
||||
#include <variant>
|
||||
|
||||
#include "task.hpp"
|
||||
|
||||
namespace ftxui::task {
|
||||
|
||||
/// A task queue that schedules tasks to be executed in the future. Tasks can be
|
||||
/// scheduled to be executed immediately, or after a certain duration.
|
||||
/// - The tasks are executed in the order they were scheduled.
|
||||
/// - If multiple tasks are scheduled to be executed at the same time, they are
|
||||
/// executed in the order they were scheduled.
|
||||
/// - If a task is scheduled to be executed in the past, it is executed
|
||||
/// immediately.
|
||||
struct TaskQueue {
|
||||
auto PostTask(PendingTask task) -> void;
|
||||
|
||||
using MaybeTask =
|
||||
std::variant<Task, std::chrono::steady_clock::duration, std::monostate>;
|
||||
auto Get() -> MaybeTask;
|
||||
|
||||
private:
|
||||
std::queue<PendingTask> immediate_tasks_;
|
||||
std::priority_queue<PendingTask> delayed_tasks_;
|
||||
};
|
||||
|
||||
} // namespace ftxui::task
|
||||
|
||||
|
||||
#endif
|
76
src/ftxui/core/task_runner.cpp
Normal file
76
src/ftxui/core/task_runner.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2024 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
#include "task_runner.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <thread>
|
||||
|
||||
namespace ftxui::task {
|
||||
|
||||
static thread_local TaskRunner* current_task_runner = nullptr; // NOLINT
|
||||
|
||||
TaskRunner::TaskRunner() {
|
||||
assert(!previous_task_runner_);
|
||||
previous_task_runner_ = current_task_runner;
|
||||
current_task_runner = this;
|
||||
}
|
||||
|
||||
TaskRunner::~TaskRunner() {
|
||||
current_task_runner = previous_task_runner_;
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
auto TaskRunner::Current() -> TaskRunner* {
|
||||
assert(current_task_runner);
|
||||
return current_task_runner;
|
||||
}
|
||||
|
||||
auto TaskRunner::PostTask(Task task) -> void {
|
||||
queue_.PostTask(PendingTask{std::move(task)});
|
||||
}
|
||||
|
||||
auto TaskRunner::PostDelayedTask(Task task,
|
||||
std::chrono::steady_clock::duration duration)
|
||||
-> void {
|
||||
queue_.PostTask(PendingTask{std::move(task), duration});
|
||||
}
|
||||
|
||||
/// Runs the tasks in the queue.
|
||||
auto TaskRunner::RunUntilIdle()
|
||||
-> std::optional<std::chrono::steady_clock::duration> {
|
||||
while (true) {
|
||||
auto maybe_task = queue_.Get();
|
||||
if (std::holds_alternative<std::monostate>(maybe_task)) {
|
||||
// No more tasks to execute, exit the loop.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (std::holds_alternative<Task>(maybe_task)) {
|
||||
std::get<Task>(maybe_task)();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::holds_alternative<std::chrono::steady_clock::duration>(
|
||||
maybe_task)) {
|
||||
return std::get<std::chrono::steady_clock::duration>(maybe_task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto TaskRunner::Run() -> void {
|
||||
while (true) {
|
||||
auto duration = RunUntilIdle();
|
||||
if (!duration) {
|
||||
// No more tasks to execute, exit the loop.
|
||||
return;
|
||||
}
|
||||
|
||||
// Sleep for the duration until the next task can be executed.
|
||||
std::this_thread::sleep_for(duration.value());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ftxui::task
|
||||
|
42
src/ftxui/core/task_runner.hpp
Normal file
42
src/ftxui/core/task_runner.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2024 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
#ifndef TASK_RUNNER_HPP
|
||||
#define TASK_RUNNER_HPP
|
||||
|
||||
#include "task.hpp"
|
||||
#include "task_queue.hpp"
|
||||
|
||||
namespace ftxui::task {
|
||||
|
||||
class TaskRunner {
|
||||
public:
|
||||
TaskRunner();
|
||||
~TaskRunner();
|
||||
|
||||
// Returns the task runner for the current thread.
|
||||
static auto Current() -> TaskRunner*;
|
||||
|
||||
/// Schedules a task to be executed immediately.
|
||||
auto PostTask(Task task) -> void;
|
||||
|
||||
/// Schedules a task to be executed after a certain duration.
|
||||
auto PostDelayedTask(Task task,
|
||||
std::chrono::steady_clock::duration duration) -> void;
|
||||
|
||||
/// Runs the tasks in the queue, return the delay until the next delayed task
|
||||
/// can be executed.
|
||||
auto RunUntilIdle() -> std::optional<std::chrono::steady_clock::duration>;
|
||||
|
||||
// Runs the tasks in the queue, blocking until all tasks are executed.
|
||||
auto Run() -> void;
|
||||
|
||||
private:
|
||||
TaskRunner* previous_task_runner_ = nullptr;
|
||||
TaskQueue queue_;
|
||||
};
|
||||
|
||||
} // namespace ftxui::task
|
||||
|
||||
|
||||
#endif // TASK_RUNNER_HPP
|
95
src/ftxui/core/task_test.cpp
Normal file
95
src/ftxui/core/task_test.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2024 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
// Copyright 2024 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
#include "task.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "task_runner.hpp"
|
||||
|
||||
namespace ftxui::task {
|
||||
|
||||
TEST(TaskTest, Basic) {
|
||||
std::vector<int> values;
|
||||
|
||||
auto task_1 = [&values] { values.push_back(1); };
|
||||
auto task_2 = [&values] { values.push_back(2); };
|
||||
auto task_3 = [&values] { values.push_back(3); };
|
||||
|
||||
auto runner = TaskRunner();
|
||||
|
||||
runner.PostTask(task_1);
|
||||
runner.PostTask(task_2);
|
||||
runner.PostTask(task_3);
|
||||
while (true) {
|
||||
auto duration = runner.RunUntilIdle();
|
||||
if (!duration) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(duration.value());
|
||||
}
|
||||
|
||||
EXPECT_EQ(values, (std::vector<int>{1, 2, 3}));
|
||||
}
|
||||
|
||||
TEST(TaskTest, PostedWithinTask) {
|
||||
std::vector<int> values;
|
||||
|
||||
auto task_1 = [&values] {
|
||||
values.push_back(1);
|
||||
auto task_2 = [&values] { values.push_back(5); };
|
||||
TaskRunner::Current()->PostTask(std::move(task_2));
|
||||
values.push_back(2);
|
||||
};
|
||||
|
||||
auto task_2 = [&values] {
|
||||
values.push_back(3);
|
||||
auto task_2 = [&values] { values.push_back(6); };
|
||||
TaskRunner::Current()->PostTask(std::move(task_2));
|
||||
values.push_back(4);
|
||||
};
|
||||
|
||||
auto runner = TaskRunner();
|
||||
|
||||
runner.PostTask(task_1);
|
||||
runner.PostTask(task_2);
|
||||
while (true) {
|
||||
auto duration = runner.RunUntilIdle();
|
||||
if (!duration) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(duration.value());
|
||||
}
|
||||
|
||||
EXPECT_EQ(values, (std::vector<int>{1, 2, 3, 4, 5, 6}));
|
||||
}
|
||||
|
||||
TEST(TaskTest, RunDelayedTask) {
|
||||
std::vector<int> values;
|
||||
|
||||
auto task_1 = [&values] { values.push_back(1); };
|
||||
auto task_2 = [&values] { values.push_back(2); };
|
||||
auto task_3 = [&values] { values.push_back(3); };
|
||||
|
||||
auto runner = TaskRunner();
|
||||
|
||||
runner.PostDelayedTask(task_3, std::chrono::milliseconds(300));
|
||||
runner.PostDelayedTask(task_1, std::chrono::milliseconds(100));
|
||||
runner.PostDelayedTask(task_2, std::chrono::milliseconds(200));
|
||||
while (true) {
|
||||
auto duration = runner.RunUntilIdle();
|
||||
if (!duration) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(duration.value());
|
||||
}
|
||||
|
||||
EXPECT_EQ(values, (std::vector<int>{1, 2, 3}));
|
||||
}
|
||||
|
||||
} // namespace ftxui::task
|
||||
|
||||
|
Reference in New Issue
Block a user