mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-11-04 05:28:15 +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