mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-11-01 02:58:12 +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
	 ArthurSonzogni
					ArthurSonzogni