mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-21 11:08:09 +08:00
Remove pthread dependency
Some checks are pending
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Waiting to run
Build / Test modules (llvm, ubuntu-latest) (push) Waiting to run
Documentation / documentation (push) Waiting to run
Some checks are pending
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Waiting to run
Build / Test modules (llvm, ubuntu-latest) (push) Waiting to run
Documentation / documentation (push) Waiting to run
This commit is contained in:
@@ -12,9 +12,15 @@ export module ftxui.component.receiver;
|
||||
* @brief The FTXUI ftxui:: namespace
|
||||
*/
|
||||
export namespace ftxui {
|
||||
// Deprecated:
|
||||
using ftxui::SenderImpl;
|
||||
// Deprecated:
|
||||
using ftxui::ReceiverImpl;
|
||||
// Deprecated:
|
||||
using ftxui::Sender;
|
||||
// Deprecated:
|
||||
using ftxui::Receiver;
|
||||
// Deprecated:
|
||||
using ftxui::MakeReceiver;
|
||||
// Deprecated:
|
||||
}
|
||||
|
@@ -1,81 +0,0 @@
|
||||
// Copyright 2020 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 <thread> // for thread
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/receiver.hpp"
|
||||
#include "gtest/gtest.h" // for AssertionResult, Message, Test, TestPartResult, EXPECT_EQ, EXPECT_TRUE, EXPECT_FALSE, TEST
|
||||
|
||||
// NOLINTBEGIN
|
||||
namespace ftxui {
|
||||
|
||||
TEST(Receiver, Basic) {
|
||||
auto receiver = MakeReceiver<char>();
|
||||
auto sender = receiver->MakeSender();
|
||||
|
||||
sender->Send('a');
|
||||
sender->Send('b');
|
||||
sender->Send('c');
|
||||
sender.reset();
|
||||
|
||||
char a, b, c, d;
|
||||
EXPECT_TRUE(receiver->Receive(&a));
|
||||
EXPECT_TRUE(receiver->Receive(&b));
|
||||
EXPECT_TRUE(receiver->Receive(&c));
|
||||
EXPECT_FALSE(receiver->Receive(&d));
|
||||
|
||||
EXPECT_EQ(a, 'a');
|
||||
EXPECT_EQ(b, 'b');
|
||||
EXPECT_EQ(c, 'c');
|
||||
}
|
||||
|
||||
TEST(Receiver, BasicWithThread) {
|
||||
auto r1 = MakeReceiver<char>();
|
||||
auto r2 = MakeReceiver<char>();
|
||||
auto r3 = MakeReceiver<char>();
|
||||
|
||||
auto s1 = r1->MakeSender();
|
||||
auto s2 = r2->MakeSender();
|
||||
auto s3 = r3->MakeSender();
|
||||
|
||||
auto s1_bis = r1->MakeSender();
|
||||
|
||||
auto stream = [](Receiver<char> receiver, Sender<char> sender) {
|
||||
char c;
|
||||
while (receiver->Receive(&c))
|
||||
sender->Send(c);
|
||||
};
|
||||
|
||||
// Convert data from a different thread.
|
||||
auto t12 = std::thread(stream, std::move(r1), std::move(s2));
|
||||
auto t23 = std::thread(stream, std::move(r2), std::move(s3));
|
||||
|
||||
// Send some data.
|
||||
s1->Send('1');
|
||||
s1_bis->Send('2');
|
||||
s1->Send('3');
|
||||
s1_bis->Send('4');
|
||||
|
||||
// Close the stream.
|
||||
s1.reset();
|
||||
s1_bis.reset();
|
||||
|
||||
char c;
|
||||
EXPECT_TRUE(r3->Receive(&c));
|
||||
EXPECT_EQ(c, '1');
|
||||
EXPECT_TRUE(r3->Receive(&c));
|
||||
EXPECT_EQ(c, '2');
|
||||
EXPECT_TRUE(r3->Receive(&c));
|
||||
EXPECT_EQ(c, '3');
|
||||
EXPECT_TRUE(r3->Receive(&c));
|
||||
EXPECT_EQ(c, '4');
|
||||
EXPECT_FALSE(r3->Receive(&c));
|
||||
|
||||
// Thread will end at the end of the stream.
|
||||
t12.join();
|
||||
t23.join();
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
// NOLINTEND
|
@@ -17,24 +17,22 @@
|
||||
#include <memory>
|
||||
#include <stack> // for stack
|
||||
#include <string>
|
||||
#include <thread> // for thread, sleep_for
|
||||
#include <tuple> // for _Swallow_assign, ignore
|
||||
#include <type_traits> // for decay_t
|
||||
#include <utility> // for move, swap
|
||||
#include <variant> // for visit, variant
|
||||
#include <vector> // for vector
|
||||
#include <thread> // for thread, sleep_for
|
||||
#include <tuple> // for _Swallow_assign, ignore
|
||||
#include <utility> // for move, swap
|
||||
#include <variant> // for visit, variant
|
||||
#include <vector> // for vector
|
||||
#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/component/loop.hpp" // for Loop
|
||||
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, Sender, MakeReceiver, SenderImpl, Receiver
|
||||
#include "ftxui/component/task_runner.hpp"
|
||||
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
|
||||
#include "ftxui/dom/node.hpp" // for Node, Render
|
||||
#include "ftxui/dom/requirement.hpp" // for Requirement
|
||||
#include "ftxui/screen/pixel.hpp" // for Pixel
|
||||
#include "ftxui/screen/terminal.hpp" // for Dimensions, Size
|
||||
#include "ftxui/screen/util.hpp" // for util::clamp
|
||||
#include "ftxui/util/autoreset.hpp" // for AutoReset
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define DEFINE_CONSOLEV2_PROPERTIES
|
||||
@@ -47,9 +45,11 @@
|
||||
#error Must be compiled in UNICODE mode
|
||||
#endif
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set, timeval
|
||||
#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
|
||||
#include <unistd.h> // for STDIN_FILENO, read
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
// Quick exit is missing in standard CLang headers
|
||||
@@ -59,6 +59,20 @@
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
struct ScreenInteractive::Internal {
|
||||
// Convert char to Event.
|
||||
TerminalInputParser terminal_input_parser;
|
||||
|
||||
task::TaskRunner task_runner;
|
||||
|
||||
// The last time a character was received.
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_char_time =
|
||||
std::chrono::steady_clock::now();
|
||||
|
||||
explicit Internal(std::function<void(Event)> out)
|
||||
: terminal_input_parser(std::move(out)) {}
|
||||
};
|
||||
|
||||
namespace animation {
|
||||
void RequestAnimationFrame() {
|
||||
auto* screen = ScreenInteractive::Active();
|
||||
@@ -82,75 +96,9 @@ constexpr int timeout_milliseconds = 20;
|
||||
timeout_milliseconds * 1000;
|
||||
#if defined(_WIN32)
|
||||
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
auto console = GetStdHandle(STD_INPUT_HANDLE);
|
||||
auto parser =
|
||||
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
|
||||
while (!*quit) {
|
||||
// Throttle ReadConsoleInput by waiting 250ms, this wait function will
|
||||
// return if there is input in the console.
|
||||
auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
|
||||
if (wait_result == WAIT_TIMEOUT) {
|
||||
parser.Timeout(timeout_milliseconds);
|
||||
continue;
|
||||
}
|
||||
|
||||
DWORD number_of_events = 0;
|
||||
if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
|
||||
continue;
|
||||
if (number_of_events <= 0)
|
||||
continue;
|
||||
|
||||
std::vector<INPUT_RECORD> records{number_of_events};
|
||||
DWORD number_of_events_read = 0;
|
||||
ReadConsoleInput(console, records.data(), (DWORD)records.size(),
|
||||
&number_of_events_read);
|
||||
records.resize(number_of_events_read);
|
||||
|
||||
for (const auto& r : records) {
|
||||
switch (r.EventType) {
|
||||
case KEY_EVENT: {
|
||||
auto key_event = r.Event.KeyEvent;
|
||||
// ignore UP key events
|
||||
if (key_event.bKeyDown == FALSE)
|
||||
continue;
|
||||
std::wstring wstring;
|
||||
wstring += key_event.uChar.UnicodeChar;
|
||||
for (auto it : to_string(wstring)) {
|
||||
parser.Add(it);
|
||||
}
|
||||
} break;
|
||||
case WINDOW_BUFFER_SIZE_EVENT:
|
||||
out->Send(Event::Special({0}));
|
||||
break;
|
||||
case MENU_EVENT:
|
||||
case FOCUS_EVENT:
|
||||
case MOUSE_EVENT:
|
||||
// TODO(mauve): Implement later.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
#include <emscripten.h>
|
||||
|
||||
// Read char from the terminal.
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
auto parser =
|
||||
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
|
||||
|
||||
char c;
|
||||
while (!*quit) {
|
||||
while (read(STDIN_FILENO, &c, 1), c)
|
||||
parser.Add(c);
|
||||
|
||||
emscripten_sleep(1);
|
||||
parser.Timeout(1);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void ftxui_on_resize(int columns, int rows) {
|
||||
@@ -164,8 +112,8 @@ void ftxui_on_resize(int columns, int rows) {
|
||||
|
||||
#else // POSIX (Linux & Mac)
|
||||
|
||||
int CheckStdinReady(int usec_timeout) {
|
||||
timeval tv = {0, usec_timeout}; // NOLINT
|
||||
int CheckStdinReady() {
|
||||
timeval tv = {0, 0}; // NOLINT
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds); // NOLINT
|
||||
FD_SET(STDIN_FILENO, &fds); // NOLINT
|
||||
@@ -173,25 +121,6 @@ int CheckStdinReady(int usec_timeout) {
|
||||
return FD_ISSET(STDIN_FILENO, &fds); // NOLINT
|
||||
}
|
||||
|
||||
// Read char from the terminal.
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
auto parser =
|
||||
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
|
||||
|
||||
while (!*quit) {
|
||||
if (!CheckStdinReady(timeout_microseconds)) {
|
||||
parser.Timeout(timeout_milliseconds);
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t buffer_size = 100;
|
||||
std::array<char, buffer_size> buffer; // NOLINT;
|
||||
size_t l = read(fileno(stdin), buffer.data(), buffer_size); // NOLINT
|
||||
for (size_t i = 0; i < l; ++i) {
|
||||
parser.Add(buffer[i]); // NOLINT
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::stack<Closure> on_exit_functions; // NOLINT
|
||||
@@ -338,15 +267,6 @@ class CapturedMouseImpl : public CapturedMouseInterface {
|
||||
std::function<void(void)> callback_;
|
||||
};
|
||||
|
||||
void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
// Animation at around 60fps.
|
||||
const auto time_delta = std::chrono::milliseconds(15);
|
||||
while (!*quit) {
|
||||
out->Send(AnimationTask());
|
||||
std::this_thread::sleep_for(time_delta);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ScreenInteractive::ScreenInteractive(Dimension dimension,
|
||||
@@ -356,7 +276,8 @@ ScreenInteractive::ScreenInteractive(Dimension dimension,
|
||||
: Screen(dimx, dimy),
|
||||
dimension_(dimension),
|
||||
use_alternative_screen_(use_alternative_screen) {
|
||||
task_receiver_ = MakeReceiver<Task>();
|
||||
internal_ = std::make_unique<Internal>(
|
||||
[&](Event event) { PostEvent(std::move(event)); });
|
||||
}
|
||||
|
||||
// static
|
||||
@@ -417,6 +338,8 @@ ScreenInteractive ScreenInteractive::TerminalOutput() {
|
||||
};
|
||||
}
|
||||
|
||||
ScreenInteractive::~ScreenInteractive() = default;
|
||||
|
||||
/// Create a ScreenInteractive whose width and height match the component being
|
||||
/// drawn.
|
||||
// static
|
||||
@@ -452,13 +375,9 @@ void ScreenInteractive::TrackMouse(bool enable) {
|
||||
/// @brief Add a task to the main loop.
|
||||
/// It will be executed later, after every other scheduled tasks.
|
||||
void ScreenInteractive::Post(Task task) {
|
||||
// Task/Events sent toward inactive screen or screen waiting to become
|
||||
// inactive are dropped.
|
||||
if (!task_sender_) {
|
||||
return;
|
||||
}
|
||||
|
||||
task_sender_->Send(std::move(task));
|
||||
internal_->task_runner.PostTask([this, task = std::move(task)]() mutable {
|
||||
HandleTask(component_, task);
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief Add an event to the main loop.
|
||||
@@ -502,7 +421,7 @@ void ScreenInteractive::Loop(Component component) { // NOLINT
|
||||
|
||||
/// @brief Return whether the main loop has been quit.
|
||||
bool ScreenInteractive::HasQuitted() {
|
||||
return task_receiver_->HasQuitted();
|
||||
return quit_;
|
||||
}
|
||||
|
||||
// private
|
||||
@@ -659,7 +578,15 @@ void ScreenInteractive::Install() {
|
||||
|
||||
SetConsoleMode(stdin_handle, in_mode);
|
||||
SetConsoleMode(stdout_handle, out_mode);
|
||||
#else
|
||||
#else // POSIX (Linux & Mac)
|
||||
// #if defined(__EMSCRIPTEN__)
|
||||
//// Reading stdin isn't blocking.
|
||||
// int flags = fcntl(0, F_GETFL, 0);
|
||||
// fcntl(0, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
//// Restore the terminal configuration on exit.
|
||||
// on_exit_functions.emplace([flags] { fcntl(0, F_SETFL, flags); });
|
||||
// #endif
|
||||
for (const int signal : {SIGWINCH, SIGTSTP}) {
|
||||
InstallSignalHandler(signal);
|
||||
}
|
||||
@@ -732,40 +659,57 @@ void ScreenInteractive::Install() {
|
||||
Flush();
|
||||
|
||||
quit_ = false;
|
||||
task_sender_ = task_receiver_->MakeSender();
|
||||
event_listener_ =
|
||||
std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
|
||||
animation_listener_ =
|
||||
std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
|
||||
|
||||
PostAnimationTask();
|
||||
}
|
||||
|
||||
// private
|
||||
void ScreenInteractive::Uninstall() {
|
||||
ExitNow();
|
||||
event_listener_.join();
|
||||
animation_listener_.join();
|
||||
OnExit();
|
||||
}
|
||||
|
||||
// private
|
||||
// NOLINTNEXTLINE
|
||||
void ScreenInteractive::RunOnceBlocking(Component component) {
|
||||
ExecuteSignalHandlers();
|
||||
Task task;
|
||||
if (task_receiver_->Receive(&task)) {
|
||||
HandleTask(component, task);
|
||||
// Set FPS to 60 at most.
|
||||
const auto time_per_frame = std::chrono::microseconds(16666); // 1s / 60fps
|
||||
|
||||
auto time = std::chrono::steady_clock::now();
|
||||
size_t executed_task = internal_->task_runner.ExecutedTasks();
|
||||
|
||||
// Wait for at least one task to execute.
|
||||
while (executed_task == internal_->task_runner.ExecutedTasks() &&
|
||||
!HasQuitted()) {
|
||||
RunOnce(component);
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto delta = now - time;
|
||||
time = now;
|
||||
|
||||
if (delta < time_per_frame) {
|
||||
const auto sleep_duration = time_per_frame - delta;
|
||||
std::this_thread::sleep_for(sleep_duration);
|
||||
}
|
||||
}
|
||||
RunOnce(component);
|
||||
}
|
||||
|
||||
// private
|
||||
void ScreenInteractive::RunOnce(Component component) {
|
||||
Task task;
|
||||
while (task_receiver_->ReceiveNonBlocking(&task)) {
|
||||
HandleTask(component, task);
|
||||
ExecuteSignalHandlers();
|
||||
AutoReset set_component(&component_, component);
|
||||
ExecuteSignalHandlers();
|
||||
FetchTerminalEvents();
|
||||
|
||||
// Execute the pending tasks from the queue.
|
||||
const size_t executed_task = internal_->task_runner.ExecutedTasks();
|
||||
internal_->task_runner.RunUntilIdle();
|
||||
// If no executed task, we can return early without redrawing the screen.
|
||||
if (executed_task == internal_->task_runner.ExecutedTasks()) {
|
||||
return;
|
||||
}
|
||||
Draw(std::move(component));
|
||||
|
||||
ExecuteSignalHandlers();
|
||||
Draw(component);
|
||||
|
||||
if (selection_data_previous_ != selection_data_) {
|
||||
selection_data_previous_ = selection_data_;
|
||||
@@ -786,6 +730,7 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
|
||||
// clang-format off
|
||||
// Handle Event.
|
||||
if constexpr (std::is_same_v<T, Event>) {
|
||||
|
||||
if (arg.is_cursor_position()) {
|
||||
cursor_x_ = arg.cursor_x();
|
||||
cursor_y_ = arg.cursor_y();
|
||||
@@ -1037,7 +982,6 @@ void ScreenInteractive::Exit() {
|
||||
// private:
|
||||
void ScreenInteractive::ExitNow() {
|
||||
quit_ = true;
|
||||
task_sender_.reset();
|
||||
}
|
||||
|
||||
// private:
|
||||
@@ -1070,6 +1014,117 @@ void ScreenInteractive::Signal(int signal) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void ScreenInteractive::FetchTerminalEvents() {
|
||||
#if defined(_WIN32)
|
||||
auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
|
||||
// Check if there is input in the console.
|
||||
auto console = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD number_of_events = 0;
|
||||
if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
|
||||
return std::vector<INPUT_RECORD>();
|
||||
}
|
||||
if (number_of_events <= 0) {
|
||||
// No input, return.
|
||||
return std::vector<INPUT_RECORD>();
|
||||
}
|
||||
// Read the input events.
|
||||
std::vector<INPUT_RECORD> records(number_of_events);
|
||||
DWORD number_of_events_read = 0;
|
||||
if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
|
||||
&number_of_events_read)) {
|
||||
return std::vector<INPUT_RECORD>();
|
||||
}
|
||||
records.resize(number_of_events_read);
|
||||
return records;
|
||||
};
|
||||
|
||||
auto records = get_input_records();
|
||||
if (records.size() == 0) {
|
||||
const auto timeout =
|
||||
std::chrono::steady_clock::now() - internal_->last_char_time;
|
||||
const size_t timeout_microseconds =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
|
||||
internal_->terminal_input_parser.Timeout(timeout_microseconds);
|
||||
return;
|
||||
}
|
||||
internal_->last_char_time = std::chrono::steady_clock::now();
|
||||
|
||||
// Convert the input events to FTXUI events.
|
||||
// For each event, we call the terminal input parser to convert it to
|
||||
// Event.
|
||||
for (const auto& r : records) {
|
||||
switch (r.EventType) {
|
||||
case KEY_EVENT: {
|
||||
auto key_event = r.Event.KeyEvent;
|
||||
// ignore UP key events
|
||||
if (key_event.bKeyDown == FALSE)
|
||||
continue;
|
||||
std::wstring wstring;
|
||||
wstring += key_event.uChar.UnicodeChar;
|
||||
for (auto it : to_string(wstring)) {
|
||||
internal_->terminal_input_parser.Add(it);
|
||||
}
|
||||
} break;
|
||||
case WINDOW_BUFFER_SIZE_EVENT:
|
||||
Post(Event::Special({0}));
|
||||
break;
|
||||
case MENU_EVENT:
|
||||
case FOCUS_EVENT:
|
||||
case MOUSE_EVENT:
|
||||
// TODO(mauve): Implement later.
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
// Read chars from the terminal.
|
||||
// We configured it to be non blocking.
|
||||
std::array<char, 128> out{};
|
||||
size_t l = read(STDIN_FILENO, out.data(), out.size());
|
||||
if (l == 0) {
|
||||
const auto timeout =
|
||||
std::chrono::steady_clock::now() - internal_->last_char_time;
|
||||
const size_t timeout_microseconds =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
|
||||
internal_->terminal_input_parser.Timeout(timeout_microseconds);
|
||||
return;
|
||||
}
|
||||
internal_->last_char_time = std::chrono::steady_clock::now();
|
||||
|
||||
// Convert the chars to events.
|
||||
for (size_t i = 0; i < l; ++i) {
|
||||
internal_->terminal_input_parser.Add(out[i]);
|
||||
}
|
||||
#else // POSIX (Linux & Mac)
|
||||
if (!CheckStdinReady()) {
|
||||
const auto timeout =
|
||||
std::chrono::steady_clock::now() - internal_->last_char_time;
|
||||
const size_t timeout_ms =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
|
||||
internal_->terminal_input_parser.Timeout(timeout_ms);
|
||||
return;
|
||||
}
|
||||
internal_->last_char_time = std::chrono::steady_clock::now();
|
||||
|
||||
// Read chars from the terminal.
|
||||
std::array<char, 128> out{};
|
||||
size_t l = read(fileno(stdin), out.data(), out.size());
|
||||
|
||||
// Convert the chars to events.
|
||||
for (size_t i = 0; i < l; ++i) {
|
||||
internal_->terminal_input_parser.Add(out[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ScreenInteractive::PostAnimationTask() {
|
||||
Post(AnimationTask());
|
||||
|
||||
// Repeat the animation task every 15ms. This correspond to a frame rate
|
||||
// of around 66fps.
|
||||
internal_->task_runner.PostDelayedTask([this] { PostAnimationTask(); },
|
||||
std::chrono::milliseconds(15));
|
||||
}
|
||||
|
||||
bool ScreenInteractive::SelectionData::operator==(
|
||||
const ScreenInteractive::SelectionData& other) const {
|
||||
if (empty && other.empty) {
|
||||
|
20
src/ftxui/component/task.cpp
Normal file
20
src/ftxui/component/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 "ftxui/component/task_internal.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/component/task_internal.hpp
Normal file
42
src/ftxui/component/task_internal.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 t) : task(std::move(t)) {} // NOLINT
|
||||
|
||||
// Delayed task with a duration
|
||||
PendingTask(Task t, std::chrono::steady_clock::duration duration)
|
||||
: task(std::move(t)),
|
||||
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/component/task_queue.cpp
Normal file
54
src/ftxui/component/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 "ftxui/component/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
|
||||
|
40
src/ftxui/component/task_queue.hpp
Normal file
40
src/ftxui/component/task_queue.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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 "ftxui/component/task_internal.hpp" // for PendingTask, Task
|
||||
|
||||
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;
|
||||
|
||||
bool HasImmediateTasks() const {
|
||||
return !immediate_tasks_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<PendingTask> immediate_tasks_;
|
||||
std::priority_queue<PendingTask> delayed_tasks_;
|
||||
};
|
||||
|
||||
} // namespace ftxui::task
|
||||
|
||||
|
||||
#endif
|
77
src/ftxui/component/task_runner.cpp
Normal file
77
src/ftxui/component/task_runner.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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 "ftxui/component/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)) {
|
||||
executed_tasks_++;
|
||||
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
|
||||
|
49
src/ftxui/component/task_runner.hpp
Normal file
49
src/ftxui/component/task_runner.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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 "ftxui/component/task_internal.hpp"
|
||||
#include "ftxui/component/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;
|
||||
|
||||
bool HasImmediateTasks() const {
|
||||
return queue_.HasImmediateTasks();
|
||||
}
|
||||
|
||||
size_t ExecutedTasks() const { return executed_tasks_; }
|
||||
|
||||
private:
|
||||
TaskRunner* previous_task_runner_ = nullptr;
|
||||
TaskQueue queue_;
|
||||
size_t executed_tasks_ = 0;
|
||||
};
|
||||
|
||||
} // namespace ftxui::task
|
||||
|
||||
|
||||
#endif // TASK_RUNNER_HPP
|
94
src/ftxui/component/task_test.cpp
Normal file
94
src/ftxui/component/task_test.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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 "ftxui/component/task_internal.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <thread> // for sleep_for
|
||||
#include "ftxui/component/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