aboutsummaryrefslogtreecommitdiff
path: root/src/base/platform/unix/EventLoop.cpp
blob: 0aac41348406f51ce29def8748c0bc8114726906 (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
116
117
118
119
120
121
122
123
124
#include "cru/base/platform/unix/EventLoop.h"
#include "cru/base/Exception.h"

#include <poll.h>
#include <algorithm>
#include <chrono>
#include <thread>

namespace cru::platform::unix {
int UnixTimerFile::GetReadFd() const { return this->read_fd_; }

UnixEventLoop::UnixEventLoop() : timer_tag_(1) {}

int UnixEventLoop::Run() {
  running_thread_ = std::this_thread::get_id();

  pollfd poll_fds[1];

  while (!exit_code_) {
    int poll_timeout = -1;

    auto iter = std::ranges::find_if(timers_, [](const TimerData &timer) {
      return timer.timeout <= std::chrono::milliseconds::zero();
    });
    if (iter != timers_.end()) {
      auto &timer = *iter;
      if (timer.repeat) {
        while (timer.timeout <= std::chrono::milliseconds::zero()) {
          timer.timeout += timer.original_timeout;
          timer.action();
        }
      } else {
        auto action = timer.action;
        timers_.erase(iter);
        action();
      }
      continue;
    }

    while (ReadTimerPipe()) {
      continue;
    }

    if (!timers_.empty()) {
      poll_timeout =
          std::ranges::min_element(timers_, [](const TimerData &left,
                                               const TimerData &right) {
            return left.timeout < right.timeout;
          })->timeout.count();
    }

    auto start = std::chrono::steady_clock::now();

    ::poll(poll_fds, sizeof poll_fds / sizeof *poll_fds, poll_timeout);

    // TODO: A Big Implement to handle X events.

    auto end = std::chrono::steady_clock::now();
    auto time =
        std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

    for (auto &timer : timers_) {
      timer.timeout -= time;
    }
  }

  return exit_code_.value();
}

void UnixEventLoop::RequestQuit(int exit_code) {}

int UnixEventLoop::SetTimer(std::function<void()> action,
                            std::chrono::milliseconds timeout, bool repeat) {
  if (repeat) {
    if (timeout <= std::chrono::milliseconds::zero()) {
      throw Exception("Interval must be bigger than 0.");
    }
  } else {
    if (timeout < std::chrono::milliseconds::zero()) {
      throw Exception("Timeout must be at least 0.");
    }
  }

  auto tag = timer_tag_++;

  if (std::this_thread::get_id() == running_thread_) {
    timers_.push_back(
        TimerData(tag, std::move(timeout), repeat, std::move(action)));
  } else {
    // TODO: Implement
  }

  return tag;
}

bool UnixEventLoop::ReadTimerPipe() {
  TimerData *pointer;
  constexpr size_t pointer_size = sizeof(decltype(pointer));
  auto rest = pointer_size;
  while (true) {
    auto result = timer_pipe_read_end_.Read(&pointer, rest);

    if (result == -1) {  // If no data.
      if (rest == pointer_size) {
        return false;
      } else {
        continue;  // Try read again (might spin), as we are in the middle of
                   // reading a pointer.
      }
    }

    if (result == 0) {
      throw Exception("Unexpected EOF of the timer pipe.");
    }

    rest -= result;
  }

  timers_.push_back(std::move(*pointer));
  delete pointer;

  return true;
}
}  // namespace cru::platform::unix