#include "logger.hh" #include "looper.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace looper { namespace { inline short events_looper2poll(uint8_t events) { short ret = 0; if (events & EVENT_READ) // NOLINTNEXTLINE(misc-include-cleaner) ret |= POLLIN | POLLPRI; if (events & EVENT_WRITE) // NOLINTNEXTLINE(misc-include-cleaner) ret |= POLLOUT; return ret; } inline uint8_t events_poll2looper(short events) { uint8_t ret = 0; // NOLINTNEXTLINE(misc-include-cleaner) if (events & (POLLIN | POLLPRI | POLLHUP)) ret |= EVENT_READ; // NOLINTNEXTLINE(misc-include-cleaner) if (events & POLLOUT) ret |= EVENT_WRITE; // NOLINTNEXTLINE(misc-include-cleaner) if (events & (POLLERR | POLLNVAL)) ret |= EVENT_ERROR; return ret; } class HookHelperImpl : public HookHelper { public: // NOLINTNEXTLINE(misc-include-cleaner) HookHelperImpl(std::vector& pollfd, int& timeout) : pollfd_(pollfd), timeout_(timeout) {} void monitor(int fd, uint8_t events) override { // NOLINTNEXTLINE(misc-include-cleaner) struct pollfd tmp; tmp.fd = fd; tmp.events = events_looper2poll(events); pollfd_.push_back(tmp); } void timeout(std::chrono::milliseconds timeout) override { int new_timeout; if (timeout.count() <= std::numeric_limits::max()) { new_timeout = static_cast(timeout.count()); } else { new_timeout = std::numeric_limits::max(); } if (timeout_ == -1) { timeout_ = new_timeout; } else { timeout_ = std::min(timeout_, new_timeout); } } private: std::vector& pollfd_; int& timeout_; }; class LooperPoll : public Looper { public: LooperPoll() = default; void add(int fd, uint8_t events, std::function callback) override { if (fd < 0) return; auto ret = entry_.emplace(fd, Entry(events)); if (!ret.second) { assert(ret.first->second.delete_); ret.first->second.delete_ = false; ret.first->second.events_ = events; } ret.first->second.callback_ = std::move(callback); } void update(int fd, uint8_t events) override { if (fd < 0) return; auto it = entry_.find(fd); if (it == entry_.end() || it->second.delete_) { assert(false); return; } it->second.events_ = events; } void remove(int fd) override { if (fd < 0) return; auto it = entry_.find(fd); if (it == entry_.end()) return; it->second.delete_ = true; } bool run(logger::Logger& logger) override { while (!quit_) { int timeout; if (scheduled_.empty()) { timeout = -1; } else { auto now = std::chrono::steady_clock::now(); while (true) { if (now < scheduled_.front().target_) { auto delay = std::chrono::duration_cast( scheduled_.front().target_ - now); if (delay.count() <= std::numeric_limits::max()) timeout = static_cast(delay.count()); else timeout = std::numeric_limits::max(); break; } auto id = scheduled_.front().id_; auto callback = std::move(scheduled_.front().callback_); scheduled_.pop_front(); callback(id); if (scheduled_.empty()) { timeout = -1; break; } } // Scheduled callbacks might call quit(). if (quit_) break; } // NOLINTNEXTLINE(misc-include-cleaner) std::vector pollfd; pollfd.reserve(entry_.size()); { auto it = entry_.begin(); while (it != entry_.end()) { if (it->second.delete_) { it = entry_.erase(it); } else { // NOLINTNEXTLINE(misc-include-cleaner) struct pollfd tmp; tmp.fd = it->first; tmp.events = events_looper2poll(it->second.events_); pollfd.push_back(tmp); ++it; } } } if (!hooks_.empty()) { HookHelperImpl helper(pollfd, timeout); auto it = hooks_.begin(); while (it != hooks_.end()) { if (it->delete_) { it = hooks_.erase(it); } else { it->hook_->before(helper); ++it; } } } // NOLINTNEXTLINE(misc-include-cleaner) int active = poll(pollfd.data(), pollfd.size(), timeout); if (active < 0) { if (errno == EINTR) continue; logger.err(std::format("Poll failed: {}", strerror(errno))); return false; } { for (auto it = pollfd.begin(); active; ++it) { if (it->revents == 0) continue; --active; auto events = events_poll2looper(it->revents); if (events) { auto it2 = entry_.find(it->fd); if (it2 == entry_.end()) break; if (!it2->second.delete_) { events &= (it2->second.events_ | EVENT_ERROR); if (events) { it2->second.callback_(events); } } } } } { auto it = hooks_.begin(); while (it != hooks_.end()) { if (it->delete_) { it = hooks_.erase(it); } else { it->hook_->after(); ++it; } } } } // Reset quit_ so run() can be called again quit_ = false; return true; } void quit() override { quit_ = true; } uint32_t schedule(double delay, std::function callback) override { assert(delay >= 0.0); uint32_t id = next_schedule_id(); auto target = std::chrono::steady_clock::now() + std::chrono::duration_cast( std::chrono::duration(delay)); auto insert = scheduled_.end(); while (insert != scheduled_.begin()) { auto prev = insert - 1; if (prev->target_ < target) break; insert = prev; } scheduled_.emplace(insert, std::move(callback), id, target); return id; } void cancel(uint32_t id) override { auto it = std::ranges::find_if(scheduled_, [id](auto const& scheduled) { return scheduled.id_ == id; }); if (it != scheduled_.end()) { scheduled_.erase(it); } else { assert(false); } } void add_hook(Hook* hook) override { assert(hook); hooks_.emplace_back(hook); } void remove_hook(Hook* hook) override { auto it = std::ranges::find_if( hooks_, [hook](auto const& entry) { return entry.hook_ == hook; }); if (it != hooks_.end()) it->delete_ = true; } private: struct Entry { uint8_t events_; std::function callback_; bool delete_{false}; explicit Entry(uint8_t events) : events_(events) {} }; struct HookEntry { Hook* hook_; bool delete_{false}; explicit HookEntry(Hook* hook) : hook_(hook) {} }; struct Scheduled { std::function callback_; uint32_t id_; std::chrono::steady_clock::time_point target_; Scheduled(std::function callback, uint32_t id, std::chrono::steady_clock::time_point target) : callback_(std::move(callback)), id_(id), target_(target) {} }; uint32_t next_schedule_id() { while (true) { uint32_t ret = next_schedule_id_++; if (ret) { bool found = std::ranges::any_of( scheduled_, [ret](auto const& scheduled) { return scheduled.id_ == ret; }); if (!found) return ret; } } } bool quit_{false}; std::unordered_map entry_; uint32_t next_schedule_id_{1}; std::deque scheduled_; std::vector hooks_; }; } // namespace [[nodiscard]] std::unique_ptr create() { return std::make_unique(); } } // namespace looper