mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-17 00:18:11 +08:00
Add a Producer/Consumer system.
It allow you to create the two end of a pipe: A producer and consumer. The producer can be moved into another thread. Several producer can be created if necessary. This will ease merging: https://github.com/ArthurSonzogni/FTXUI/pull/11
This commit is contained in:
@@ -36,19 +36,25 @@ Event Event::Special(const std::string& input) {
|
||||
return event;
|
||||
}
|
||||
|
||||
Event ParseUTF8(std::function<char()>& getchar, std::string& input) {
|
||||
if ((input[0] & 0b11000000) == 0b11000000)
|
||||
input += getchar();
|
||||
if ((input[0] & 0b11100000) == 0b11100000)
|
||||
input += getchar();
|
||||
if ((input[0] & 0b11110000) == 0b11110000)
|
||||
input += getchar();
|
||||
return Event::Character(input);
|
||||
void ParseUTF8(Consumer<char>& in, Producer<Event>& out, std::string& input) {
|
||||
char c;
|
||||
char mask = 0b11000000;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if ((input[0] & mask) == mask) {
|
||||
if (!in->Receive(&c))
|
||||
return;
|
||||
input += c;
|
||||
}
|
||||
mask = mask >> 1 | 0b10000000;
|
||||
}
|
||||
out->Send(Event::Character(input));
|
||||
}
|
||||
|
||||
Event ParseCSI(std::function<char()> getchar, std::string& input) {
|
||||
void ParseCSI(Consumer<char>& in, Producer<Event>& out, std::string& input) {
|
||||
char c;
|
||||
while (1) {
|
||||
char c = getchar();
|
||||
if (!in->Receive(&c))
|
||||
return;
|
||||
input += c;
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
@@ -58,80 +64,95 @@ Event ParseCSI(std::function<char()> getchar, std::string& input) {
|
||||
continue;
|
||||
|
||||
if (c >= ' ' && c <= '~')
|
||||
return Event::Special(input);
|
||||
return out->Send(Event::Special(input));
|
||||
|
||||
// Invalid ESC in CSI.
|
||||
if (c == '\x1B')
|
||||
return Event::Special(input);
|
||||
return out->Send(Event::Special(input));
|
||||
}
|
||||
}
|
||||
|
||||
Event ParseDCS(std::function<char()> getchar, std::string& input) {
|
||||
void ParseDCS(Consumer<char>& in, Producer<Event>& out, std::string& input) {
|
||||
char c;
|
||||
// Parse until the string terminator ST.
|
||||
while (1) {
|
||||
input += getchar();
|
||||
if (!in->Receive(&c))
|
||||
return;
|
||||
input += c;
|
||||
if (input.back() != '\x1B')
|
||||
continue;
|
||||
input += getchar();
|
||||
if (!in->Receive(&c))
|
||||
return;
|
||||
input += c;
|
||||
if (input.back() != '\\')
|
||||
continue;
|
||||
return Event::Special(input);
|
||||
return out->Send(Event::Special(input));
|
||||
}
|
||||
}
|
||||
|
||||
Event ParseOSC(std::function<char()> getchar, std::string& input) {
|
||||
void ParseOSC(Consumer<char>& in, Producer<Event>& out, std::string& input) {
|
||||
char c;
|
||||
// Parse until the string terminator ST.
|
||||
while (1) {
|
||||
input += getchar();
|
||||
if (!in->Receive(&c))
|
||||
return;
|
||||
input += c;
|
||||
if (input.back() != '\x1B')
|
||||
continue;
|
||||
input += getchar();
|
||||
if (!in->Receive(&c))
|
||||
return;
|
||||
input += c;
|
||||
if (input.back() != '\\')
|
||||
continue;
|
||||
return Event::Special(input);
|
||||
return out->Send(Event::Special(input));
|
||||
}
|
||||
}
|
||||
|
||||
Event ParseESC(std::function<char()> getchar, std::string& input) {
|
||||
input += getchar();
|
||||
switch (input.back()) {
|
||||
void ParseESC(Consumer<char>& in, Producer<Event>& out, std::string& input) {
|
||||
char c;
|
||||
if (!in->Receive(&c))
|
||||
return;
|
||||
input += c;
|
||||
switch (c) {
|
||||
case 'P':
|
||||
return ParseDCS(getchar, input);
|
||||
return ParseDCS(in, out, input);
|
||||
case '[':
|
||||
return ParseCSI(getchar, input);
|
||||
return ParseCSI(in, out, input);
|
||||
case ']':
|
||||
return ParseOSC(getchar, input);
|
||||
return ParseOSC(in, out, input);
|
||||
default:
|
||||
input += getchar();
|
||||
return Event::Special(input);
|
||||
if (!in->Receive(&c))
|
||||
return;
|
||||
input += c;
|
||||
out->Send(Event::Special(input));
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
Event Event::GetEvent(std::function<char()> getchar) {
|
||||
void Event::Convert(Consumer<char>& in, Producer<Event>& out, char c) {
|
||||
std::string input;
|
||||
input += getchar();
|
||||
input += c;
|
||||
|
||||
unsigned char head = input[0];
|
||||
switch (head) {
|
||||
case 24: // CAN
|
||||
case 26: // SUB
|
||||
return Event(); // Ignored.
|
||||
case 24: // CAN
|
||||
case 26: // SUB
|
||||
return;
|
||||
|
||||
case 'P':
|
||||
return ParseDCS(getchar, input);
|
||||
return ParseDCS(in, out, input);
|
||||
|
||||
case '\x1B':
|
||||
return ParseESC(getchar, input);
|
||||
return ParseESC(in, out, input);
|
||||
}
|
||||
|
||||
if (head < 32) // C0
|
||||
return Event::Special(input);
|
||||
return out->Send(Event::Special(input));
|
||||
|
||||
if (head == 127) // Delete
|
||||
return Event::Special(input);
|
||||
return out->Send(Event::Special(input));
|
||||
|
||||
return ParseUTF8(getchar, input);
|
||||
return ParseUTF8(in, out, input);
|
||||
}
|
||||
|
||||
// --- Arrow ---
|
||||
|
30
src/ftxui/component/pipe.hpp
Normal file
30
src/ftxui/component/pipe.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef FTXUI_COMPONENTS_TASK_QUEUE_H_
|
||||
#define FTXUI_COMPONENTS_TASK_QUEUE_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
|
||||
|
||||
template <class T>
|
||||
class TaskQueue {
|
||||
public:
|
||||
void Post(T task);
|
||||
void Close();
|
||||
bool Take(T& task);
|
||||
private:
|
||||
std::unique_lock<std::mutex> lock(events_queue_mutex);
|
||||
events_queue.push(event);
|
||||
events_queue_cv.notify_one();
|
||||
|
||||
std::condition_variable events_queue_cv;
|
||||
std::mutex events_queue_mutex;
|
||||
std::queue<Event> events_queue;
|
||||
std::atomic<bool> quit_ = false;
|
||||
};
|
||||
|
||||
#endif // FTXUI_COMPONENTS_TASK_QUEUE_H_
|
@@ -57,7 +57,11 @@ void OnResize(int /* signal */) {
|
||||
}
|
||||
|
||||
ScreenInteractive::ScreenInteractive(int dimx, int dimy, Dimension dimension)
|
||||
: Screen(dimx, dimy), dimension_(dimension) {}
|
||||
: Screen(dimx, dimy), dimension_(dimension) {
|
||||
event_consumer_ = MakeConsumer<Event>();
|
||||
event_producer_ = event_consumer_->MakeProducer();
|
||||
}
|
||||
|
||||
ScreenInteractive::~ScreenInteractive() {}
|
||||
|
||||
// static
|
||||
@@ -81,21 +85,7 @@ ScreenInteractive ScreenInteractive::FitComponent() {
|
||||
}
|
||||
|
||||
void ScreenInteractive::PostEvent(Event event) {
|
||||
std::unique_lock<std::mutex> lock(events_queue_mutex);
|
||||
events_queue.push(event);
|
||||
events_queue_cv.notify_one();
|
||||
}
|
||||
|
||||
void ScreenInteractive::EventLoop(Component* component) {
|
||||
std::unique_lock<std::mutex> lock(events_queue_mutex);
|
||||
while (!quit_ && events_queue.empty())
|
||||
events_queue_cv.wait(lock);
|
||||
|
||||
// After the wait, we own the lock.
|
||||
while (!events_queue.empty()) {
|
||||
component->OnEvent(events_queue.front());
|
||||
events_queue.pop();
|
||||
}
|
||||
event_producer_->Send(event);
|
||||
}
|
||||
|
||||
void ScreenInteractive::Loop(Component* component) {
|
||||
@@ -150,12 +140,25 @@ void ScreenInteractive::Loop(Component* component) {
|
||||
std::cout << std::endl;
|
||||
});
|
||||
|
||||
// Spawn a thread. It will listen for new characters being typed.
|
||||
std::thread read_char([this] {
|
||||
while (!quit_) {
|
||||
auto event = Event::GetEvent([] { return (char)getchar(); });
|
||||
PostEvent(std::move(event));
|
||||
}
|
||||
auto char_consumer = MakeConsumer<char>();
|
||||
|
||||
// Spawn a thread to produce char.
|
||||
auto char_producer = char_consumer->MakeProducer();
|
||||
std::thread read_char([&] {
|
||||
// TODO(arthursonzogni): Use a timeout so that it doesn't block even if the
|
||||
// user doesn't generate new chars.
|
||||
while (!quit_)
|
||||
char_producer->Send((char)getchar());
|
||||
char_producer.reset();
|
||||
});
|
||||
|
||||
// Spawn a thread producing events and consumer chars.
|
||||
auto event_producer = event_consumer_->MakeProducer();
|
||||
std::thread convert_char_to_event([&] {
|
||||
char c;
|
||||
while (char_consumer->Receive(&c))
|
||||
Event::Convert(char_consumer, event_producer, c);
|
||||
event_producer.reset();
|
||||
});
|
||||
|
||||
// The main loop.
|
||||
@@ -164,9 +167,12 @@ void ScreenInteractive::Loop(Component* component) {
|
||||
Draw(component);
|
||||
std::cout << ToString() << set_cursor_position << std::flush;
|
||||
Clear();
|
||||
EventLoop(component);
|
||||
Event event;
|
||||
if (event_consumer_->Receive(&event))
|
||||
component->OnEvent(event);
|
||||
}
|
||||
read_char.join();
|
||||
convert_char_to_event.join();
|
||||
OnExit(0);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user