mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-08 09:24:34 +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/terminal_input_parser.hpp
|
||||||
src/ftxui/component/util.cpp
|
src/ftxui/component/util.cpp
|
||||||
src/ftxui/component/window.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
|
target_link_libraries(dom PUBLIC screen)
|
||||||
PUBLIC screen
|
target_link_libraries(component PUBLIC dom)
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(component
|
|
||||||
PUBLIC dom
|
|
||||||
)
|
|
||||||
|
|
||||||
if (NOT EMSCRIPTEN)
|
if (NOT EMSCRIPTEN)
|
||||||
find_package(Threads)
|
find_package(Threads)
|
||||||
|
@@ -19,13 +19,13 @@ add_executable(ftxui-tests
|
|||||||
src/ftxui/component/menu_test.cpp
|
src/ftxui/component/menu_test.cpp
|
||||||
src/ftxui/component/modal_test.cpp
|
src/ftxui/component/modal_test.cpp
|
||||||
src/ftxui/component/radiobox_test.cpp
|
src/ftxui/component/radiobox_test.cpp
|
||||||
src/ftxui/util/ref_test.cpp
|
|
||||||
src/ftxui/component/receiver_test.cpp
|
src/ftxui/component/receiver_test.cpp
|
||||||
src/ftxui/component/resizable_split_test.cpp
|
src/ftxui/component/resizable_split_test.cpp
|
||||||
src/ftxui/component/screen_interactive_test.cpp
|
src/ftxui/component/screen_interactive_test.cpp
|
||||||
src/ftxui/component/slider_test.cpp
|
src/ftxui/component/slider_test.cpp
|
||||||
src/ftxui/component/terminal_input_parser_test.cpp
|
src/ftxui/component/terminal_input_parser_test.cpp
|
||||||
src/ftxui/component/toggle_test.cpp
|
src/ftxui/component/toggle_test.cpp
|
||||||
|
src/ftxui/core/task_test.cpp
|
||||||
src/ftxui/dom/blink_test.cpp
|
src/ftxui/dom/blink_test.cpp
|
||||||
src/ftxui/dom/bold_test.cpp
|
src/ftxui/dom/bold_test.cpp
|
||||||
src/ftxui/dom/border_test.cpp
|
src/ftxui/dom/border_test.cpp
|
||||||
@@ -51,6 +51,7 @@ add_executable(ftxui-tests
|
|||||||
src/ftxui/dom/vbox_test.cpp
|
src/ftxui/dom/vbox_test.cpp
|
||||||
src/ftxui/screen/color_test.cpp
|
src/ftxui/screen/color_test.cpp
|
||||||
src/ftxui/screen/string_test.cpp
|
src/ftxui/screen/string_test.cpp
|
||||||
|
src/ftxui/util/ref_test.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(ftxui-tests
|
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