mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-10 21:14:49 +08:00
Refactor FetchTerminalEvent
This commit is contained in:
@@ -9,7 +9,6 @@
|
|||||||
#include <functional> // for function
|
#include <functional> // for function
|
||||||
#include <memory> // for shared_ptr
|
#include <memory> // for shared_ptr
|
||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
#include <thread> // for thread
|
|
||||||
|
|
||||||
#include "ftxui/component/animation.hpp" // for TimePoint
|
#include "ftxui/component/animation.hpp" // for TimePoint
|
||||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||||
@@ -104,6 +103,10 @@ class ScreenInteractive : public Screen {
|
|||||||
|
|
||||||
void Signal(int signal);
|
void Signal(int signal);
|
||||||
|
|
||||||
|
void FetchTerminalEvents();
|
||||||
|
|
||||||
|
void PostAnimationTask();
|
||||||
|
|
||||||
ScreenInteractive* suspended_screen_ = nullptr;
|
ScreenInteractive* suspended_screen_ = nullptr;
|
||||||
enum class Dimension {
|
enum class Dimension {
|
||||||
FitComponent,
|
FitComponent,
|
||||||
@@ -127,8 +130,6 @@ class ScreenInteractive : public Screen {
|
|||||||
std::string reset_cursor_position;
|
std::string reset_cursor_position;
|
||||||
|
|
||||||
std::atomic<bool> quit_{false};
|
std::atomic<bool> quit_{false};
|
||||||
std::thread event_listener_;
|
|
||||||
std::thread animation_listener_;
|
|
||||||
bool animation_requested_ = false;
|
bool animation_requested_ = false;
|
||||||
animation::TimePoint previous_animation_time_;
|
animation::TimePoint previous_animation_time_;
|
||||||
|
|
||||||
@@ -163,10 +164,15 @@ class ScreenInteractive : public Screen {
|
|||||||
std::unique_ptr<Selection> selection_;
|
std::unique_ptr<Selection> selection_;
|
||||||
std::function<void()> selection_on_change_;
|
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;
|
friend class Loop;
|
||||||
|
|
||||||
|
Component component_;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class Private {
|
class Private {
|
||||||
public:
|
public:
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stack> // for stack
|
#include <stack> // for stack
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <array>
|
||||||
#include <thread> // for thread, sleep_for
|
#include <thread> // for thread, sleep_for
|
||||||
#include <tuple> // for _Swallow_assign, ignore
|
#include <tuple> // for _Swallow_assign, ignore
|
||||||
#include <type_traits> // for decay_t
|
#include <type_traits> // for decay_t
|
||||||
@@ -60,6 +61,20 @@
|
|||||||
|
|
||||||
namespace ftxui {
|
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 {
|
namespace animation {
|
||||||
void RequestAnimationFrame() {
|
void RequestAnimationFrame() {
|
||||||
auto* screen = ScreenInteractive::Active();
|
auto* screen = ScreenInteractive::Active();
|
||||||
@@ -174,25 +189,25 @@ int CheckStdinReady(int usec_timeout) {
|
|||||||
return FD_ISSET(STDIN_FILENO, &fds); // NOLINT
|
return FD_ISSET(STDIN_FILENO, &fds); // NOLINT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Read char from the terminal.
|
// Read char from the terminal.
|
||||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
//void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||||
auto parser =
|
//auto parser =
|
||||||
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
|
//TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
|
||||||
|
|
||||||
while (!*quit) {
|
//while (!*quit) {
|
||||||
if (!CheckStdinReady(timeout_microseconds)) {
|
//auto pending = ReadPendingChars();
|
||||||
parser.Timeout(timeout_milliseconds);
|
//if (pending.empty()) {
|
||||||
continue;
|
//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
|
#endif
|
||||||
|
|
||||||
std::stack<Closure> on_exit_functions; // NOLINT
|
std::stack<Closure> on_exit_functions; // NOLINT
|
||||||
@@ -339,15 +354,6 @@ class CapturedMouseImpl : public CapturedMouseInterface {
|
|||||||
std::function<void(void)> callback_;
|
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
|
} // namespace
|
||||||
|
|
||||||
ScreenInteractive::ScreenInteractive(Dimension dimension,
|
ScreenInteractive::ScreenInteractive(Dimension dimension,
|
||||||
@@ -357,7 +363,8 @@ ScreenInteractive::ScreenInteractive(Dimension dimension,
|
|||||||
: Screen(dimx, dimy),
|
: Screen(dimx, dimy),
|
||||||
dimension_(dimension),
|
dimension_(dimension),
|
||||||
use_alternative_screen_(use_alternative_screen) {
|
use_alternative_screen_(use_alternative_screen) {
|
||||||
task_receiver_ = MakeReceiver<Task>();
|
internal_ = std::make_unique<Internal>(
|
||||||
|
[&](Event event) { PostEvent(std::move(event)); });
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@@ -455,13 +462,9 @@ void ScreenInteractive::TrackMouse(bool enable) {
|
|||||||
/// @brief Add a task to the main loop.
|
/// @brief Add a task to the main loop.
|
||||||
/// It will be executed later, after every other scheduled tasks.
|
/// It will be executed later, after every other scheduled tasks.
|
||||||
void ScreenInteractive::Post(Task task) {
|
void ScreenInteractive::Post(Task task) {
|
||||||
// Task/Events sent toward inactive screen or screen waiting to become
|
internal_->task_runner.PostTask([this, task = std::move(task)]() mutable {
|
||||||
// inactive are dropped.
|
HandleTask(component_, task);
|
||||||
if (!task_sender_) {
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
task_sender_->Send(std::move(task));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Add an event to the main loop.
|
/// @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.
|
/// @brief Return whether the main loop has been quit.
|
||||||
bool ScreenInteractive::HasQuitted() {
|
bool ScreenInteractive::HasQuitted() {
|
||||||
return task_receiver_->HasQuitted();
|
return quit_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// private
|
// private
|
||||||
@@ -735,39 +738,41 @@ void ScreenInteractive::Install() {
|
|||||||
Flush();
|
Flush();
|
||||||
|
|
||||||
quit_ = false;
|
quit_ = false;
|
||||||
task_sender_ = task_receiver_->MakeSender();
|
|
||||||
event_listener_ =
|
PostAnimationTask();
|
||||||
std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
|
|
||||||
animation_listener_ =
|
|
||||||
std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private
|
// private
|
||||||
void ScreenInteractive::Uninstall() {
|
void ScreenInteractive::Uninstall() {
|
||||||
ExitNow();
|
ExitNow();
|
||||||
event_listener_.join();
|
|
||||||
animation_listener_.join();
|
|
||||||
OnExit();
|
OnExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// private
|
// private
|
||||||
// NOLINTNEXTLINE
|
// NOLINTNEXTLINE
|
||||||
void ScreenInteractive::RunOnceBlocking(Component component) {
|
void ScreenInteractive::RunOnceBlocking(Component component) {
|
||||||
ExecuteSignalHandlers();
|
size_t executed_task = internal_->task_runner.ExecutedTasks();
|
||||||
Task task;
|
while(executed_task == internal_->task_runner.ExecutedTasks() &&
|
||||||
if (task_receiver_->Receive(&task)) {
|
!HasQuitted()) {
|
||||||
HandleTask(component, task);
|
RunOnce(component);
|
||||||
}
|
}
|
||||||
RunOnce(component);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private
|
// private
|
||||||
void ScreenInteractive::RunOnce(Component component) {
|
void ScreenInteractive::RunOnce(Component component) {
|
||||||
Task task;
|
component_ = component;
|
||||||
while (task_receiver_->ReceiveNonBlocking(&task)) {
|
ExecuteSignalHandlers();
|
||||||
HandleTask(component, task);
|
FetchTerminalEvents();
|
||||||
ExecuteSignalHandlers();
|
|
||||||
|
// 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));
|
Draw(std::move(component));
|
||||||
|
|
||||||
if (selection_data_previous_ != selection_data_) {
|
if (selection_data_previous_ != selection_data_) {
|
||||||
@@ -777,6 +782,8 @@ void ScreenInteractive::RunOnce(Component component) {
|
|||||||
Post(Event::Custom);
|
Post(Event::Custom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
component_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// private
|
// private
|
||||||
@@ -1040,7 +1047,6 @@ void ScreenInteractive::Exit() {
|
|||||||
// private:
|
// private:
|
||||||
void ScreenInteractive::ExitNow() {
|
void ScreenInteractive::ExitNow() {
|
||||||
quit_ = true;
|
quit_ = true;
|
||||||
task_sender_.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private:
|
// private:
|
||||||
@@ -1073,6 +1079,36 @@ void ScreenInteractive::Signal(int signal) {
|
|||||||
#endif
|
#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==(
|
bool ScreenInteractive::SelectionData::operator==(
|
||||||
const ScreenInteractive::SelectionData& other) const {
|
const ScreenInteractive::SelectionData& other) const {
|
||||||
if (empty && other.empty) {
|
if (empty && other.empty) {
|
||||||
|
@@ -25,6 +25,10 @@ struct TaskQueue {
|
|||||||
std::variant<Task, std::chrono::steady_clock::duration, std::monostate>;
|
std::variant<Task, std::chrono::steady_clock::duration, std::monostate>;
|
||||||
auto Get() -> MaybeTask;
|
auto Get() -> MaybeTask;
|
||||||
|
|
||||||
|
bool HasImmediateTasks() const {
|
||||||
|
return !immediate_tasks_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::queue<PendingTask> immediate_tasks_;
|
std::queue<PendingTask> immediate_tasks_;
|
||||||
std::priority_queue<PendingTask> delayed_tasks_;
|
std::priority_queue<PendingTask> delayed_tasks_;
|
||||||
|
@@ -48,6 +48,7 @@ auto TaskRunner::RunUntilIdle()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (std::holds_alternative<Task>(maybe_task)) {
|
if (std::holds_alternative<Task>(maybe_task)) {
|
||||||
|
executed_tasks_++;
|
||||||
std::get<Task>(maybe_task)();
|
std::get<Task>(maybe_task)();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@@ -31,9 +31,16 @@ class TaskRunner {
|
|||||||
// Runs the tasks in the queue, blocking until all tasks are executed.
|
// Runs the tasks in the queue, blocking until all tasks are executed.
|
||||||
auto Run() -> void;
|
auto Run() -> void;
|
||||||
|
|
||||||
|
bool HasImmediateTasks() const {
|
||||||
|
return queue_.HasImmediateTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ExecutedTasks() const { return executed_tasks_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TaskRunner* previous_task_runner_ = nullptr;
|
TaskRunner* previous_task_runner_ = nullptr;
|
||||||
TaskQueue queue_;
|
TaskQueue queue_;
|
||||||
|
size_t executed_tasks_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ftxui::task
|
} // namespace ftxui::task
|
||||||
|
Reference in New Issue
Block a user