Refactor FetchTerminalEvent

This commit is contained in:
ArthurSonzogni
2025-07-06 18:26:35 +02:00
parent 41116fe8bd
commit f017897b36
5 changed files with 109 additions and 55 deletions

View File

@@ -9,7 +9,6 @@
#include <functional> // for function
#include <memory> // for shared_ptr
#include <string> // for string
#include <thread> // for thread
#include "ftxui/component/animation.hpp" // for TimePoint
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
@@ -104,6 +103,10 @@ class ScreenInteractive : public Screen {
void Signal(int signal);
void FetchTerminalEvents();
void PostAnimationTask();
ScreenInteractive* suspended_screen_ = nullptr;
enum class Dimension {
FitComponent,
@@ -127,8 +130,6 @@ class ScreenInteractive : public Screen {
std::string reset_cursor_position;
std::atomic<bool> quit_{false};
std::thread event_listener_;
std::thread animation_listener_;
bool animation_requested_ = false;
animation::TimePoint previous_animation_time_;
@@ -163,10 +164,15 @@ class ScreenInteractive : public Screen {
std::unique_ptr<Selection> selection_;
std::function<void()> selection_on_change_;
std::unique_ptr<task::TaskRunner> task_runner_;
// PIMPL private implementation idiom (Pimpl).
struct Internal;
std::unique_ptr<Internal> internal_;
friend class Loop;
Component component_;
public:
class Private {
public:

View File

@@ -17,6 +17,7 @@
#include <memory>
#include <stack> // for stack
#include <string>
#include <array>
#include <thread> // for thread, sleep_for
#include <tuple> // for _Swallow_assign, ignore
#include <type_traits> // for decay_t
@@ -60,6 +61,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();
@@ -174,25 +189,25 @@ 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)); });
//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;
}
//while (!*quit) {
//auto pending = ReadPendingChars();
//if (pending.empty()) {
//parser.Timeout(timeout_milliseconds);
//continue;
//}
//for (auto c : pending) {
//parser.Add(c);
//}
//}
//}
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
@@ -339,15 +354,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,
@@ -357,7 +363,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
@@ -455,13 +462,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.
@@ -505,7 +508,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
@@ -735,39 +738,41 @@ 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);
size_t executed_task = internal_->task_runner.ExecutedTasks();
while(executed_task == internal_->task_runner.ExecutedTasks() &&
!HasQuitted()) {
RunOnce(component);
}
RunOnce(component);
}
// private
void ScreenInteractive::RunOnce(Component component) {
Task task;
while (task_receiver_->ReceiveNonBlocking(&task)) {
HandleTask(component, task);
ExecuteSignalHandlers();
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;
}
ExecuteSignalHandlers();
Draw(std::move(component));
if (selection_data_previous_ != selection_data_) {
@@ -777,6 +782,8 @@ void ScreenInteractive::RunOnce(Component component) {
Post(Event::Custom);
}
}
component_.reset();
}
// private
@@ -1040,7 +1047,6 @@ void ScreenInteractive::Exit() {
// private:
void ScreenInteractive::ExitNow() {
quit_ = true;
task_sender_.reset();
}
// private:
@@ -1073,6 +1079,36 @@ void ScreenInteractive::Signal(int signal) {
#endif
}
void ScreenInteractive::FetchTerminalEvents() {
if (!CheckStdinReady(timeout_microseconds)) {
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]);
}
}
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) {

View File

@@ -25,6 +25,10 @@ struct TaskQueue {
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_;

View File

@@ -48,6 +48,7 @@ auto TaskRunner::RunUntilIdle()
}
if (std::holds_alternative<Task>(maybe_task)) {
executed_tasks_++;
std::get<Task>(maybe_task)();
continue;
}

View File

@@ -31,9 +31,16 @@ class TaskRunner {
// 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