diff --git a/CMakeLists.txt b/CMakeLists.txt index 14c77f7..9c57698 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,7 +73,7 @@ add_library(component include/ftxui/component/menu.hpp include/ftxui/component/radiobox.hpp include/ftxui/component/screen_interactive.hpp - include/ftxui/component/producer_consumer.hpp + include/ftxui/component/receiver.hpp include/ftxui/component/toggle.hpp ) diff --git a/include/ftxui/component/event.hpp b/include/ftxui/component/event.hpp index 01c5e13..d02e9e0 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -2,10 +2,10 @@ #define FTXUI_COMPONENT_EVENT_HPP #include +#include #include #include #include -#include namespace ftxui { @@ -21,7 +21,7 @@ struct Event { static Event Character(const std::string&); static Event Special(const std::string&); - static void Convert(Consumer& in, Producer& out, char c); + static void Convert(Receiver& in, Sender& out, char c); // --- Arrow --- static Event ArrowLeft; diff --git a/include/ftxui/component/producer_consumer.hpp b/include/ftxui/component/producer_consumer.hpp deleted file mode 100644 index 82d4f71..0000000 --- a/include/ftxui/component/producer_consumer.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef FTXUI_COMPONENTS_CONSUMER_PRODUCER_H_ -#define FTXUI_COMPONENTS_CONSUMER_PRODUCER_H_ - -#include -#include -#include -#include -#include -#include - -namespace ftxui { - -// Usage: -// -// Initialization: -// --------------- -// -// auto consumer = MakeConsumer(); -// auto producer_1 = consumer.MakeProducer(); -// auto producer_2 = consumer.MakeProducer(); -// -// Then move one producers elsewhere, potentially in a different thread. -// ---------------------- -// [thread 1] producer_1->Send("hello"); -// [thread 2] producer_2->Send("world"); -// -// On the consumer side: -// --------------------- -// char c; -// while(consumer_->Receive(&c)) // Return true as long as there is a producer. -// print(c) -// -// Consumer::Receive returns true when the last Producer is released. - -// clang-format off -template class ProducerImpl; -template class ConsumerImpl; -template using Producer = std::unique_ptr>; -template using Consumer = std::unique_ptr>; -template Consumer MakeConsumer(); -// clang-format on - -// ---- Implementation part ---- - -template -class ProducerImpl { - public: - void Send(T t) { consumer_->Receive(std::move(t)); } - ~ProducerImpl() { consumer_->producers_--; } - - private: - friend class ConsumerImpl; - ProducerImpl(ConsumerImpl* consumer) : consumer_(consumer) {} - ConsumerImpl* consumer_; -}; - -template -class ConsumerImpl { - public: - Producer MakeProducer() { - producers_++; - return std::unique_ptr>(new ProducerImpl(this)); - } - - bool Receive(T* t) { - while (producers_) { - std::unique_lock lock(mutex_); - while (queue_.empty()) - notifier_.wait(lock); - if (queue_.empty()) - continue; - *t = std::move(queue_.front()); - queue_.pop(); - return true; - } - return false; - } - - private: - friend class ProducerImpl; - - void Receive(T t) { - std::unique_lock lock(mutex_); - queue_.push(std::move(t)); - notifier_.notify_one(); - } - - std::mutex mutex_; - std::queue queue_; - std::condition_variable notifier_; - std::atomic producers_ = 0; -}; - -template -Consumer MakeConsumer() { - return std::make_unique>(); -} - -} // namespace ftxui - -#endif // FTXUI_COMPONENTS_CONSUMER_PRODUCER_H_ diff --git a/include/ftxui/component/receiver.hpp b/include/ftxui/component/receiver.hpp new file mode 100644 index 0000000..7fe7400 --- /dev/null +++ b/include/ftxui/component/receiver.hpp @@ -0,0 +1,103 @@ +#ifndef FTXUI_COMPONENT_RECEIVER_HPP_ +#define FTXUI_COMPONENT_RECEIVER_HPP_ + +#include +#include +#include +#include +#include +#include + +namespace ftxui { + +// Usage: +// +// Initialization: +// --------------- +// +// auto receiver = MakeReceiver(); +// auto sender_1= receiver.MakeSender(); +// auto sender_2 = receiver.MakeSender(); +// +// Then move the senders elsewhere, potentially in a different thread. +// +// On the producer side: +// ---------------------- +// [thread 1] sender_1->Send("hello"); +// [thread 2] sender_2->Send("world"); +// +// On the consumer side: +// --------------------- +// char c; +// while(receiver->Receive(&c)) // Return true as long as there is a producer. +// print(c) +// +// Receiver::Receive() returns true when there are no more senders. + +// clang-format off +template class SenderImpl; +template class ReceiverImpl; +template using Sender = std::unique_ptr>; +template using Receiver = std::unique_ptr>; +template Receiver MakeReceiver(); +// clang-format on + +// ---- Implementation part ---- + +template +class SenderImpl { + public: + void Send(T t) { sender_->Receive(std::move(t)); } + ~SenderImpl() { sender_->senders_--; } + + private: + friend class ReceiverImpl; + SenderImpl(ReceiverImpl* consumer) : sender_(consumer) {} + ReceiverImpl* sender_; +}; + +template +class ReceiverImpl { + public: + Sender MakeSender() { + senders_++; + return std::unique_ptr>(new SenderImpl(this)); + } + + bool Receive(T* t) { + while (senders_) { + std::unique_lock lock(mutex_); + while (queue_.empty()) + notifier_.wait(lock); + if (queue_.empty()) + continue; + *t = std::move(queue_.front()); + queue_.pop(); + return true; + } + return false; + } + + private: + friend class SenderImpl; + + void Receive(T t) { + std::unique_lock lock(mutex_); + queue_.push(std::move(t)); + notifier_.notify_one(); + } + + std::mutex mutex_; + std::queue queue_; + std::condition_variable notifier_; + std::atomic senders_ = 0; +}; + +template +Receiver MakeReceiver() { + return std::make_unique>(); +} + +} // namespace ftxui + +#endif // FTXUI_COMPONENT_RECEIVER_HPP_ diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 0dcb1d8..3e0426b 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -10,7 +11,6 @@ #include "ftxui/component/event.hpp" #include "ftxui/screen/screen.hpp" -#include namespace ftxui { class Component; @@ -41,13 +41,13 @@ class ScreenInteractive : public Screen { Dimension dimension_ = Dimension::Fixed; ScreenInteractive(int dimx, int dimy, Dimension dimension); - Producer event_producer_; - Consumer event_consumer_; + Sender event_sender_; + Receiver event_receiver_; std::string set_cursor_position; std::string reset_cursor_position; - std::atomicquit_ = false; + std::atomic quit_ = false; }; } // namespace ftxui diff --git a/src/ftxui/component/event.cpp b/src/ftxui/component/event.cpp index b6c8d4a..ec03648 100644 --- a/src/ftxui/component/event.cpp +++ b/src/ftxui/component/event.cpp @@ -36,7 +36,7 @@ Event Event::Special(const std::string& input) { return event; } -void ParseUTF8(Consumer& in, Producer& out, std::string& input) { +void ParseUTF8(Receiver& in, Sender& out, std::string& input) { char c; char mask = 0b11000000; for (int i = 0; i < 3; ++i) { @@ -50,7 +50,7 @@ void ParseUTF8(Consumer& in, Producer& out, std::string& input) { out->Send(Event::Character(input)); } -void ParseCSI(Consumer& in, Producer& out, std::string& input) { +void ParseCSI(Receiver& in, Sender& out, std::string& input) { char c; while (1) { if (!in->Receive(&c)) @@ -72,7 +72,7 @@ void ParseCSI(Consumer& in, Producer& out, std::string& input) { } } -void ParseDCS(Consumer& in, Producer& out, std::string& input) { +void ParseDCS(Receiver& in, Sender& out, std::string& input) { char c; // Parse until the string terminator ST. while (1) { @@ -90,7 +90,7 @@ void ParseDCS(Consumer& in, Producer& out, std::string& input) { } } -void ParseOSC(Consumer& in, Producer& out, std::string& input) { +void ParseOSC(Receiver& in, Sender& out, std::string& input) { char c; // Parse until the string terminator ST. while (1) { @@ -108,7 +108,7 @@ void ParseOSC(Consumer& in, Producer& out, std::string& input) { } } -void ParseESC(Consumer& in, Producer& out, std::string& input) { +void ParseESC(Receiver& in, Sender& out, std::string& input) { char c; if (!in->Receive(&c)) return; @@ -129,7 +129,7 @@ void ParseESC(Consumer& in, Producer& out, std::string& input) { } // static -void Event::Convert(Consumer& in, Producer& out, char c) { +void Event::Convert(Receiver& in, Sender& out, char c) { std::string input; input += c; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 2f4d375..6887ea2 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -58,8 +58,8 @@ void OnResize(int /* signal */) { ScreenInteractive::ScreenInteractive(int dimx, int dimy, Dimension dimension) : Screen(dimx, dimy), dimension_(dimension) { - event_consumer_ = MakeConsumer(); - event_producer_ = event_consumer_->MakeProducer(); + event_receiver_ = MakeReceiver(); + event_sender_ = event_receiver_->MakeSender(); } ScreenInteractive::~ScreenInteractive() {} @@ -85,7 +85,7 @@ ScreenInteractive ScreenInteractive::FitComponent() { } void ScreenInteractive::PostEvent(Event event) { - event_producer_->Send(event); + event_sender_->Send(event); } void ScreenInteractive::Loop(Component* component) { @@ -140,25 +140,25 @@ void ScreenInteractive::Loop(Component* component) { std::cout << std::endl; }); - auto char_consumer = MakeConsumer(); + auto char_receiver = MakeReceiver(); // Spawn a thread to produce char. - auto char_producer = char_consumer->MakeProducer(); + auto char_sender = char_receiver->MakeSender(); 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(); + char_sender->Send((char)getchar()); + char_sender.reset(); }); // Spawn a thread producing events and consumer chars. - auto event_producer = event_consumer_->MakeProducer(); + auto event_sender = event_receiver_->MakeSender(); std::thread convert_char_to_event([&] { char c; - while (char_consumer->Receive(&c)) - Event::Convert(char_consumer, event_producer, c); - event_producer.reset(); + while (char_receiver->Receive(&c)) + Event::Convert(char_receiver, event_sender, c); + event_sender.reset(); }); // The main loop. @@ -168,7 +168,7 @@ void ScreenInteractive::Loop(Component* component) { std::cout << ToString() << set_cursor_position << std::flush; Clear(); Event event; - if (event_consumer_->Receive(&event)) + if (event_receiver_->Receive(&event)) component->OnEvent(event); } read_char.join();