aboutsummaryrefslogtreecommitdiff
path: root/include/cru/base/platform/unix/EventLoop.h
blob: a3fac1763010a052511cf21b5c8f7074b6182d68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#pragma once

#if !defined(__unix) && !defined(__APPLE__)
#error "This file can only be included on unix."
#endif

#include "../../Base.h"
#include "../../Exception.h"
#include "UnixFile.h"

#include <poll.h>
#include <unistd.h>
#include <chrono>
#include <optional>
#include <thread>
#include <utility>

namespace cru::platform::unix {
class UnixTimerFile : public Object2 {
 public:
  template <class Rep, class Period>
  explicit UnixTimerFile(std::chrono::duration<Rep, Period> time) {
    auto fds = OpenUniDirectionalPipe();
    this->read_fd_ = std::move(fds.read);
    this->write_fd_ = std::move(fds.write);

    this->thread_ = std::thread([this, time] {
      std::this_thread::sleep_for(time);
      constexpr auto buffer = "";
      auto written = ::write(this->write_fd_, buffer, 1);
      if (written != 1) {
        throw Exception(
            "Failed to write to pipe in UnixTimerFile thread at timeout.");
      }
    });
    this->thread_.detach();
  }

  int GetReadFd() const;

 private:
  UnixFileDescriptor write_fd_;
  UnixFileDescriptor read_fd_;
  std::thread thread_;
};

class UnixEventLoop : public Object2 {
  CRU_DEFINE_CLASS_LOG_TAG("cru::platform::unix::UnixEventLoop")
 public:
  UnixEventLoop();

  int Run();
  void RequestQuit(int exit_code = 0);

  int SetTimer(std::function<void()> action, std::chrono::milliseconds timeout,
               bool repeat);
  void CancelTimer(int id);

  int SetImmediate(std::function<void()> action) {
    return this->SetTimer(std::move(action), std::chrono::milliseconds::zero(),
                          false);
  }

  int SetTimeout(std::function<void()> action,
                 std::chrono::milliseconds timeout) {
    return this->SetTimer(std::move(action), std::move(timeout), false);
  }

  int SetInterval(std::function<void()> action,
                  std::chrono::milliseconds interval) {
    return this->SetTimer(std::move(action), std::move(interval), true);
  }

 private:
  struct TimerData {
    int id;
    std::chrono::milliseconds original_timeout;
    std::chrono::milliseconds timeout;
    bool repeat;
    std::function<void()> action;

    TimerData(int id, std::chrono::milliseconds timeout, bool repeat,
              std::function<void()> action)
        : id(id),
          original_timeout(timeout),
          timeout(timeout),
          repeat(repeat),
          action(std::move(action)) {}
  };

 private:
  bool CheckPoll();
  bool CheckTimer();
  bool ReadTimerPipe();

  void RemoveTimer(int id);

 private:
  std::optional<std::thread::id> running_thread_;

  std::vector<pollfd> polls_;
  std::vector<
      std::function<void(decltype(std::declval<pollfd>().revents) revent)>>
      poll_actions_;

  std::atomic_int timer_tag_;
  std::vector<TimerData> timers_;

  UnixFileDescriptor timer_pipe_read_end_;
  UnixFileDescriptor timer_pipe_write_end_;

  std::optional<int> exit_code_;
};

}  // namespace cru::platform::unix