aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-09-04 02:06:20 +0800
committerYuqian Yang <crupest@crupest.life>2025-09-04 02:06:20 +0800
commitac3d88fc043d628979675dc3ef99ba5e1b4d58ca (patch)
tree6e122b7a4e119e3535b57df5cb7d17a8e034f46c
parent1b197ca9997eb407407e508eb199b1e6fae78926 (diff)
downloadcru-ac3d88fc043d628979675dc3ef99ba5e1b4d58ca.tar.gz
cru-ac3d88fc043d628979675dc3ef99ba5e1b4d58ca.tar.bz2
cru-ac3d88fc043d628979675dc3ef99ba5e1b4d58ca.zip
Add unix event loop.
-rw-r--r--include/cru/base/Base.h1
-rw-r--r--include/cru/base/platform/unix/EventLoop.h95
-rw-r--r--include/cru/base/platform/unix/Timer.h43
-rw-r--r--src/base/Base.cpp1
-rw-r--r--src/base/CMakeLists.txt2
-rw-r--r--src/base/platform/unix/EventLoop.cpp92
-rw-r--r--src/base/platform/unix/Timer.cpp5
-rw-r--r--test/base/CMakeLists.txt2
-rw-r--r--test/base/platform/unix/EventLoopTest.cpp (renamed from test/base/platform/unix/TimerTest.cpp)2
9 files changed, 192 insertions, 51 deletions
diff --git a/include/cru/base/Base.h b/include/cru/base/Base.h
index fd409360..f8f8c8c0 100644
--- a/include/cru/base/Base.h
+++ b/include/cru/base/Base.h
@@ -93,6 +93,7 @@ struct CRU_BASE_API Interface {
};
[[noreturn]] void CRU_BASE_API UnreachableCode();
+[[noreturn]] void CRU_BASE_API NotImplemented();
using Index = std::ptrdiff_t;
diff --git a/include/cru/base/platform/unix/EventLoop.h b/include/cru/base/platform/unix/EventLoop.h
new file mode 100644
index 00000000..bc2bcbf8
--- /dev/null
+++ b/include/cru/base/platform/unix/EventLoop.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <optional>
+#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 <unistd.h>
+#include <chrono>
+#include <thread>
+
+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 {
+ public:
+ UnixEventLoop();
+
+ int Run();
+ void RequestQuit(int exit_code = 0);
+
+ int SetTimer(std::function<void()> action, std::chrono::milliseconds timeout,
+ bool repeat);
+
+ 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)) {}
+ };
+
+ std::thread::id running_thread_;
+
+ std::atomic_int timer_tag_;
+ std::vector<TimerData> timers_;
+
+ std::optional<int> exit_code_;
+};
+
+} // namespace cru::platform::unix
diff --git a/include/cru/base/platform/unix/Timer.h b/include/cru/base/platform/unix/Timer.h
deleted file mode 100644
index 17abd1cc..00000000
--- a/include/cru/base/platform/unix/Timer.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#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 <chrono>
-#include <thread>
-#include <unistd.h>
-
-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_;
-};
-} // namespace cru::platform::unix
diff --git a/src/base/Base.cpp b/src/base/Base.cpp
index 1704f8a9..c6d9ac07 100644
--- a/src/base/Base.cpp
+++ b/src/base/Base.cpp
@@ -4,4 +4,5 @@
namespace cru {
void UnreachableCode() { std::terminate(); }
+void NotImplemented() { std::terminate(); }
} // namespace cru
diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt
index 300898af..7e7b0127 100644
--- a/src/base/CMakeLists.txt
+++ b/src/base/CMakeLists.txt
@@ -24,8 +24,8 @@ target_compile_definitions(CruBase PUBLIC $<$<CONFIG:Debug>:CRU_DEBUG>)
if (UNIX AND NOT EMSCRIPTEN)
target_sources(CruBase PRIVATE
+ platform/unix/EventLoop.cpp
platform/unix/PosixSpawnSubProcess.cpp
- platform/unix/Timer.cpp
platform/unix/UnixFile.cpp
platform/unix/UnixFileStream.cpp
)
diff --git a/src/base/platform/unix/EventLoop.cpp b/src/base/platform/unix/EventLoop.cpp
new file mode 100644
index 00000000..337d8a44
--- /dev/null
+++ b/src/base/platform/unix/EventLoop.cpp
@@ -0,0 +1,92 @@
+#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;
+ }
+
+ 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;
+}
+
+} // namespace cru::platform::unix
diff --git a/src/base/platform/unix/Timer.cpp b/src/base/platform/unix/Timer.cpp
deleted file mode 100644
index 25264dc8..00000000
--- a/src/base/platform/unix/Timer.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-#include "cru/base/platform/unix/Timer.h"
-
-namespace cru::platform::unix {
-int UnixTimerFile::GetReadFd() const { return this->read_fd_; }
-} // namespace cru::platform::unix
diff --git a/test/base/CMakeLists.txt b/test/base/CMakeLists.txt
index 5abba240..95313ccb 100644
--- a/test/base/CMakeLists.txt
+++ b/test/base/CMakeLists.txt
@@ -25,7 +25,7 @@ target_compile_definitions(CruBaseTest PRIVATE
if (UNIX AND NOT EMSCRIPTEN)
target_sources(CruBaseTest PRIVATE
- platform/unix/TimerTest.cpp
+ platform/unix/EventLoopTest.cpp
platform/unix/UnixFileTest.cpp
platform/unix/UnixFileStreamTest.cpp
)
diff --git a/test/base/platform/unix/TimerTest.cpp b/test/base/platform/unix/EventLoopTest.cpp
index dc3cc1ba..f5936b2a 100644
--- a/test/base/platform/unix/TimerTest.cpp
+++ b/test/base/platform/unix/EventLoopTest.cpp
@@ -1,4 +1,4 @@
-#include "cru/base/platform/unix/Timer.h"
+#include "cru/base/platform/unix/EventLoop.h"
#include <catch2/catch_test_macros.hpp>