From 570e48b11ee1280b227e1d57c1fb494a8e39fcf3 Mon Sep 17 00:00:00 2001 From: Weng Xuetian Date: Fri, 15 Nov 2024 17:28:25 -0800 Subject: [PATCH] Make event loop implementation swappable. --- CMakeLists.txt | 69 ++++-- src/lib/fcitx-utils/CMakeLists.txt | 52 ++--- src/lib/fcitx-utils/dbus/sdbus/bus.cpp | 13 +- src/lib/fcitx-utils/event.cpp | 21 +- src/lib/fcitx-utils/event.h | 20 ++ src/lib/fcitx-utils/event_libuv.cpp | 7 +- src/lib/fcitx-utils/event_none.cpp | 16 ++ test/CMakeLists.txt | 7 +- test/eventlooptests.cpp | 185 ++++++++++++++++ test/eventlooptests.h | 13 ++ test/testcustomeventloop.cpp | 282 +++++++++++++++++++++++++ test/testevent.cpp | 182 +--------------- 12 files changed, 633 insertions(+), 234 deletions(-) create mode 100644 src/lib/fcitx-utils/event_none.cpp create mode 100644 test/eventlooptests.cpp create mode 100644 test/eventlooptests.h create mode 100644 test/testcustomeventloop.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index aa90bad4a..3c7bc8778 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,13 +31,14 @@ option(ENABLE_DBUS "Enable DBus" On) option(ENABLE_DOC "Build doxygen" Off) option(ENABLE_SERVER "Build a fcitx as server, disable this option if you want to use fcitx as an embedded library." On) option(ENABLE_KEYBOARD "Enable key event translation with XKB and build keyboard engine" On) -option(USE_SYSTEMD "Use systemd for event loop and dbus, will fallback to libuv/libdbus if not found." On) +option(USE_SYSTEMD "Use systemd for event loop and dbus, will fallback to libuv/libdbus if not found. Only used when EVENT_LOOP_BACKEND is auto." On) option(ENABLE_XDGAUTOSTART "Enable xdg autostart desktop file installation" On) option(USE_FLATPAK_ICON "Use flatpak icon name for desktop files" Off) option(ENABLE_EMOJI "Enable emoji module" On) option(ENABLE_LIBUUID "Use libuuid for uuid generation" On) option(BUILD_SPELL_DICT "Build en_dict.fscd for English spell check" On) set(NO_PREEDIT_APPS "gvim.*,wps.*,wpp.*,et.*" CACHE STRING "Disable preedit for follwing app by default.") +set(EVENT_LOOP_BACKEND "auto" CACHE STRING "Set the underlying event loop implementation, valid values are auto,systemd,libuv,none") if (ENABLE_EMOJI) find_package(ZLIB REQUIRED) @@ -47,35 +48,67 @@ if ((ENABLE_WAYLAND OR ENABLE_X11) AND NOT ENABLE_KEYBOARD) message(FATAL_ERROR "X11 and Wayland require ENABLE_KEYBOARD to be set to ON.") endif () - ####################################################################### # Find packages ####################################################################### find_package(PkgConfig REQUIRED) -if (USE_SYSTEMD) -find_package(Systemd) -endif () +set(CANDIDATE_EVENT_LOOP_BACKENDS) -if (USE_FLATPAK_ICON) - set(FCITX_ICON_NAME "org.fcitx.Fcitx5") -else() - set(FCITX_ICON_NAME "fcitx") +if (EVENT_LOOP_BACKEND STREQUAL "auto") + if (USE_SYSTEMD) + list(APPEND CANDIDATE_EVENT_LOOP_BACKENDS systemd) + endif() + + list(APPEND CANDIDATE_EVENT_LOOP_BACKENDS libuv) +elseif (EVENT_LOOP_BACKEND STREQUAL "systemd") + list(APPEND CANDIDATE_EVENT_LOOP_BACKENDS systemd) +elseif (EVENT_LOOP_BACKEND STREQUAL "libuv") + list(APPEND CANDIDATE_EVENT_LOOP_BACKENDS libuv) +elseif (EVENT_LOOP_BACKEND STREQUAL "none") + list(APPEND CANDIDATE_EVENT_LOOP_BACKENDS none) endif() -if (NOT TARGET Systemd::Systemd) - if (ENABLE_DBUS) - pkg_check_modules(DBus REQUIRED IMPORTED_TARGET "dbus-1") - pkg_get_variable(DBUS_SYSTEM_BUS_DEFAULT_ADDRESS "dbus-1" "system_bus_default_address") - endif() +set(FCITX_EVENT_LOOP_BACKEND "") +foreach(CANDIDATE_EVENT_LOOP_BACKEND IN LISTS CANDIDATE_EVENT_LOOP_BACKENDS) + if (CANDIDATE_EVENT_LOOP_BACKEND STREQUAL systemd) + find_package(Systemd) + if (TARGET Systemd::Systemd) + set(FCITX_EVENT_LOOP_BACKEND "systemd") + break() + endif() + elseif (CANDIDATE_EVENT_LOOP_BACKEND STREQUAL libuv) + if (NOT LIBUV_TARGET) + if (NOT (TARGET PkgConfig::LibUV)) + pkg_check_modules(LibUV IMPORTED_TARGET "libuv") + set(LIBUV_TARGET PkgConfig::LibUV) + endif() + endif() - if (NOT LIBUV_TARGET) - if (NOT (TARGET PkgConfig::LibUV)) - pkg_check_modules(LibUV REQUIRED IMPORTED_TARGET "libuv") + if (TARGET LIBUV_TARGET) + set(FCITX_EVENT_LOOP_BACKEND "libuv") + break() endif() - set(LIBUV_TARGET PkgConfig::LibUV) + elseif (CANDIDATE_EVENT_LOOP_BACKEND STREQUAL none) + set(FCITX_EVENT_LOOP_BACKEND "none") + break() endif() +endforeach() + +if (ENABLE_DBUS AND NOT (FCITX_EVENT_LOOP_BACKEND STREQUAL systemd)) + pkg_check_modules(DBus REQUIRED IMPORTED_TARGET "dbus-1") + pkg_get_variable(DBUS_SYSTEM_BUS_DEFAULT_ADDRESS "dbus-1" "system_bus_default_address") +endif() + +if (FCITX_EVENT_LOOP_BACKEND STREQUAL "") + message(FATAL_ERROR "Failed to find a valid event loop backend.") +endif() + +if (USE_FLATPAK_ICON) + set(FCITX_ICON_NAME "org.fcitx.Fcitx5") +else() + set(FCITX_ICON_NAME "fcitx") endif() if(${CMAKE_SYSTEM_NAME} MATCHES "BSD|DragonFly") diff --git a/src/lib/fcitx-utils/CMakeLists.txt b/src/lib/fcitx-utils/CMakeLists.txt index e1498a14c..4fe67c8d4 100644 --- a/src/lib/fcitx-utils/CMakeLists.txt +++ b/src/lib/fcitx-utils/CMakeLists.txt @@ -1,44 +1,42 @@ set(FCITX_UTILS_SOURCES) +set(FCITX_UTILS_DEPS) if (ENABLE_DBUS) - set(FCITX_UTILS_SOURCES - ${FCITX_UTILS_SOURCES} + list(APPEND FCITX_UTILS_SOURCES dbus/servicewatcher.cpp dbus/matchrule.cpp dbus/variant.cpp dbus/objectvtable.cpp ) - if (NOT TARGET Systemd::Systemd) - set(FCITX_UTILS_SOURCES - ${FCITX_UTILS_SOURCES} - dbus/libdbus/bus.cpp - dbus/libdbus/message.cpp - dbus/libdbus/objectvtable_libdbus.cpp - dbus/libdbus/servicenamecache.cpp) - else() - set(FCITX_UTILS_SOURCES - ${FCITX_UTILS_SOURCES} + if (FCITX_EVENT_LOOP_BACKEND STREQUAL "systemd") + list(APPEND FCITX_UTILS_SOURCES dbus/sdbus/bus.cpp dbus/sdbus/message.cpp dbus/sdbus/objectvtablewrapper.c dbus/sdbus/objectvtable_sdbus.cpp) + else() + list(APPEND FCITX_UTILS_SOURCES + dbus/libdbus/bus.cpp + dbus/libdbus/message.cpp + dbus/libdbus/objectvtable_libdbus.cpp + dbus/libdbus/servicenamecache.cpp) + list(APPEND FCITX_UTILS_DEPS PkgConfig::DBus) endif() endif() -if (NOT TARGET Systemd::Systemd) - set(FCITX_UTILS_SOURCES - ${FCITX_UTILS_SOURCES} - event_libuv.cpp) -else() - set(FCITX_UTILS_SOURCES - ${FCITX_UTILS_SOURCES} - event_sdevent.cpp) +if (FCITX_EVENT_LOOP_BACKEND STREQUAL "libuv") + list(APPEND FCITX_UTILS_SOURCES event_libuv.cpp) + list(APPEND FCITX_UTILS_DEPS "${LIBUV_TARGET}") +elseif (FCITX_EVENT_LOOP_BACKEND STREQUAL "systemd") + list(APPEND FCITX_UTILS_SOURCES event_sdevent.cpp) + list(APPEND FCITX_UTILS_DEPS Systemd::Systemd) +elseif (FCITX_EVENT_LOOP_BACKEND STREQUAL "none") + list(APPEND FCITX_UTILS_SOURCES event_none.cpp) endif() -set(FCITX_UTILS_SOURCES - ${FCITX_UTILS_SOURCES} +list(APPEND FCITX_UTILS_SOURCES stringutils.cpp testing.cpp key.cpp @@ -135,15 +133,7 @@ target_link_libraries(Fcitx5Utils PRIVATE DL::DL LibIntl::LibIntl Pthread::Pthre if(LIBKVM_FOUND) target_link_libraries(Fcitx5Utils PRIVATE LibKVM::LibKVM) endif() - -if (NOT TARGET Systemd::Systemd) - target_link_libraries(Fcitx5Utils PRIVATE ${LIBUV_TARGET}) - if (ENABLE_DBUS) - target_link_libraries(Fcitx5Utils PRIVATE PkgConfig::DBus) - endif() -else() - target_link_libraries(Fcitx5Utils PRIVATE Systemd::Systemd) -endif() +target_link_libraries(Fcitx5Utils PRIVATE ${FCITX_UTILS_DEPS}) configure_file(Fcitx5Utils.pc.in ${CMAKE_CURRENT_BINARY_DIR}/Fcitx5Utils.pc @ONLY) diff --git a/src/lib/fcitx-utils/dbus/sdbus/bus.cpp b/src/lib/fcitx-utils/dbus/sdbus/bus.cpp index 768d044e4..d125b4549 100644 --- a/src/lib/fcitx-utils/dbus/sdbus/bus.cpp +++ b/src/lib/fcitx-utils/dbus/sdbus/bus.cpp @@ -6,6 +6,7 @@ */ #include +#include #include "../../log.h" #include "bus_p.h" #include "message_p.h" @@ -117,9 +118,15 @@ void Bus::attachEventLoop(EventLoop *loop) { if (d->eventLoop_) { return; } - sd_event *event = static_cast(loop->nativeHandle()); - if (sd_bus_attach_event(d->bus_, event, 0) >= 0) { - d->eventLoop_ = loop; + if (loop->implementation() == std::string_view("sd-event")) { + sd_event *event = static_cast(loop->nativeHandle()); + if (sd_bus_attach_event(d->bus_, event, 0) >= 0) { + d->eventLoop_ = loop; + } + } else { + // TODO: support sd-bus + generic event loop implementation. + throw std::invalid_argument( + "not support sd-bus with non-sdevent implementation."); } } diff --git a/src/lib/fcitx-utils/event.cpp b/src/lib/fcitx-utils/event.cpp index ce11aebf5..bdbd5f448 100644 --- a/src/lib/fcitx-utils/event.cpp +++ b/src/lib/fcitx-utils/event.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "event_p.h" #include "eventloopinterface.h" @@ -21,12 +22,28 @@ namespace fcitx { class EventLoopPrivate { public: EventLoopPrivate(std::unique_ptr impl) - : impl_(std::move(impl)) {} + : impl_(std::move(impl)) { + if (!impl_) { + throw std::runtime_error("No available event loop implementation."); + } + } std::unique_ptr impl_; + + static EventLoopFactory factory_; }; -EventLoop::EventLoop() : EventLoop(createDefaultEventLoop()) {} +EventLoopFactory EventLoopPrivate::factory_ = createDefaultEventLoop; + +void EventLoop::setEventLoopFactory(EventLoopFactory factory) { + if (factory) { + EventLoopPrivate::factory_ = std::move(factory); + } else { + EventLoopPrivate::factory_ = createDefaultEventLoop; + } +} + +EventLoop::EventLoop() : EventLoop(EventLoopPrivate::factory_()) {} EventLoop::EventLoop(std::unique_ptr impl) : d_ptr(std::make_unique(std::move(impl))) {} diff --git a/src/lib/fcitx-utils/event.h b/src/lib/fcitx-utils/event.h index 11b4e8ac6..5c24cb872 100644 --- a/src/lib/fcitx-utils/event.h +++ b/src/lib/fcitx-utils/event.h @@ -16,6 +16,8 @@ namespace fcitx { +using EventLoopFactory = std::function()>; + class EventLoopPrivate; class FCITXUTILS_EXPORT EventLoop { public: @@ -27,9 +29,18 @@ class FCITXUTILS_EXPORT EventLoop { /** * Return the default implementation name. + * + * This will only return the default implementation name. + * Do not rely on this value. + * + * @see EventLoop::implementation */ FCITXUTILS_DEPRECATED static const char *impl(); + /** + * Return the name of implementation of event loop. + * @since 5.1.12 + */ const char *implementation() const; void *nativeHandle(); @@ -45,6 +56,15 @@ class FCITXUTILS_EXPORT EventLoop { FCITX_NODISCARD std::unique_ptr addPostEvent(EventCallback callback); + /** + * Set an external event loop implementation. + * + * This is useful if you need to integrate fcitx with another event loop. + * + * @since 5.1.12 + */ + static void setEventLoopFactory(EventLoopFactory factory); + private: const std::unique_ptr d_ptr; FCITX_DECLARE_PRIVATE(EventLoop); diff --git a/src/lib/fcitx-utils/event_libuv.cpp b/src/lib/fcitx-utils/event_libuv.cpp index 6e6580cbe..1719584bc 100644 --- a/src/lib/fcitx-utils/event_libuv.cpp +++ b/src/lib/fcitx-utils/event_libuv.cpp @@ -249,7 +249,12 @@ void PostEventCallback(uv_prepare_t *handle) { source->setEnabled(false); } auto callback = source->callback_; - (*callback)(source); + auto ret = (*callback)(source); + if (sourceRef.isValid()) { + if (!ret) { + source->setEnabled(false); + } + } } catch (const std::exception &e) { // some abnormal things threw{ FCITX_FATAL() << e.what(); diff --git a/src/lib/fcitx-utils/event_none.cpp b/src/lib/fcitx-utils/event_none.cpp new file mode 100644 index 000000000..a0ea0d50d --- /dev/null +++ b/src/lib/fcitx-utils/event_none.cpp @@ -0,0 +1,16 @@ + +/* + * SPDX-FileCopyrightText: 2015-2015 CSSlayer + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ +#include "event_p.h" + +namespace fcitx { + +std::unique_ptr createDefaultEventLoop() { return nullptr; } + +const char *defaultEventLoopImplementation() { return "none"; } + +} // namespace fcitx \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bed8e37cf..2eab77989 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,6 +3,9 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(addon) +add_library(eventlooptests STATIC eventlooptests.cpp) +target_link_libraries(eventlooptests Fcitx5::Utils) + set(FCITX_UTILS_TEST testflags teststringutils @@ -11,6 +14,7 @@ set(FCITX_UTILS_TEST testcolor testi18nstring testevent + testcustomeventloop testlist testfs testlibrary @@ -33,7 +37,8 @@ set(FCITX_UTILS_DBUS_TEST set(testdbus_LIBS Pthread::Pthread) set(testeventdispatcher_LIBS Pthread::Pthread) -set(testevent_LIBS Pthread::Pthread) +set(testevent_LIBS Pthread::Pthread eventlooptests) +set(testcustomeventloop_LIBS Pthread::Pthread eventlooptests) find_program(XVFB_BIN Xvfb) diff --git a/test/eventlooptests.cpp b/test/eventlooptests.cpp new file mode 100644 index 000000000..6098a4bb3 --- /dev/null +++ b/test/eventlooptests.cpp @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2024-2024 CSSlayer + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ +#include "eventlooptests.h" +#include +#include +#include +#include "fcitx-utils/event.h" +#include "fcitx-utils/eventdispatcher.h" +#include "fcitx-utils/log.h" + +using namespace fcitx; + +void test_basic() { + EventLoop e; + + int pipefd[2]; + int r = pipe(pipefd); + FCITX_ASSERT(r == 0); + + std::unique_ptr source( + e.addIOEvent(pipefd[0], IOEventFlag::In, + [&e, pipefd](EventSource *, int fd, IOEventFlags flags) { + FCITX_ASSERT(pipefd[0] == fd); + if (flags & IOEventFlag::Hup) { + e.exit(); + } + + if (flags & IOEventFlag::In) { + char buf[20]; + auto size = read(fd, buf, 20); + if (size == 0) { + e.exit(); + } else { + FCITX_INFO() << "QUIT" << flags; + FCITX_ASSERT(size == 1); + FCITX_ASSERT(buf[0] == 'a'); + } + } + return true; + })); + + std::unique_ptr source4(e.addDeferEvent([](EventSource *) { + FCITX_INFO() << "DEFER"; + return true; + })); + + std::unique_ptr source5(e.addExitEvent([](EventSource *) { + FCITX_INFO() << "EXIT"; + return true; + })); + + int times = 10; + std::unique_ptr sourceX( + e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 1000000ul, 1, + [×](EventSource *source, uint64_t) { + FCITX_INFO() << "Recur:" << times; + times--; + if (times < 0) { + source->setEnabled(false); + } + return true; + })); + sourceX->setEnabled(true); + + int times2 = 10; + std::unique_ptr sourceX2( + e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 1000000ul, 1, + [×2](EventSourceTime *source, uint64_t t) { + FCITX_INFO() << "Recur 2:" << times2 << " " << t; + times2--; + if (times2 > 0) { + source->setNextInterval(100000); + source->setOneShot(); + } + return true; + })); + + std::unique_ptr source2( + e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 1000000ul, 0, + [pipefd](EventSource *, uint64_t) { + FCITX_INFO() << "WRITE"; + auto r = write(pipefd[1], "a", 1); + FCITX_ASSERT(r == 1); + return true; + })); + + std::unique_ptr source3( + e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 2000000ul, 0, + [pipefd](EventSource *, uint64_t) { + FCITX_INFO() << "CLOSE"; + close(pipefd[1]); + return true; + })); + + e.exec(); +} + +void test_source_deleted() { + EventLoop e; + + int pipefd[2]; + int r = pipe(pipefd); + FCITX_ASSERT(r == 0); + + std::unique_ptr source( + e.addIOEvent(pipefd[0], IOEventFlag::In, + [&source](EventSource *, int, IOEventFlags flags) { + if (flags & IOEventFlag::In) { + FCITX_INFO() << "RESET"; + source.reset(); + } + return true; + })); + + std::unique_ptr source2( + e.addDeferEvent([pipefd](EventSource *) { + FCITX_INFO() << "WRITE"; + auto r = write(pipefd[1], "a", 1); + FCITX_ASSERT(r == 1); + return true; + })); + + std::unique_ptr source3( + e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 2000000ul, 0, + [&e](EventSource *, uint64_t) { + FCITX_INFO() << "EXIT"; + e.exit(); + return true; + })); + + e.exec(); +} + +void test_post_time() { + EventLoop e; + bool ready = false; + auto post = e.addPostEvent([&ready, &e](EventSource *) { + FCITX_INFO() << "POST"; + if (ready) { + e.exit(); + } + return true; + }); + auto time = e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 1000000, + 0, [&ready](EventSource *, uint64_t) { + FCITX_INFO() << "TIME"; + ready = true; + return true; + }); + e.exec(); +} + +void test_post_io() { + EventLoop e; + EventDispatcher dispatcher; + bool ready = false; + auto post = e.addPostEvent([&ready, &e](EventSource *) { + FCITX_INFO() << "POST"; + if (ready) { + e.exit(); + } + return true; + }); + dispatcher.attach(&e); + std::thread thread([&dispatcher, &ready]() { + sleep(2); + dispatcher.schedule([&ready]() { + FCITX_INFO() << "DISPATCHER"; + ready = true; + }); + }); + e.exec(); + thread.join(); +} + +void runAllEventLoopTests() { + test_basic(); + test_source_deleted(); + test_post_time(); + test_post_io(); +} diff --git a/test/eventlooptests.h b/test/eventlooptests.h new file mode 100644 index 000000000..0b17d8b85 --- /dev/null +++ b/test/eventlooptests.h @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2024-2024 CSSlayer + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#ifndef _TEST_EVENTLOOPTESTS_H_ +#define _TEST_EVENTLOOPTESTS_H_ + +void runAllEventLoopTests(); + +#endif \ No newline at end of file diff --git a/test/testcustomeventloop.cpp b/test/testcustomeventloop.cpp new file mode 100644 index 000000000..5c7cff90a --- /dev/null +++ b/test/testcustomeventloop.cpp @@ -0,0 +1,282 @@ +/* + * SPDX-FileCopyrightText: 2015-2015 CSSlayer + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#include +#include +#include +#include +#include +#include +#include "fcitx-utils/eventdispatcher.h" +#include "fcitx-utils/eventloopinterface.h" +#include "fcitx-utils/intrusivelist.h" +#include "fcitx-utils/trackableobject.h" +#include "eventlooptests.h" + +using namespace fcitx; + +enum class PollSourceEnableState { Disabled = 0, Oneshot = 1, Enabled = 2 }; + +template +class PollEventBase : public T, public TrackableObject> { +public: + PollEventBase(PollSourceEnableState state, C callback) + : state_(state), callback_(std::move(callback)) {} + + bool isEnabled() const override { + return state_ != PollSourceEnableState::Disabled; + } + void setEnabled(bool enabled) override { + auto newState = enabled ? PollSourceEnableState::Enabled + : PollSourceEnableState::Disabled; + setState(newState); + } + + void setOneShot() override { setState(PollSourceEnableState::Oneshot); } + + bool isOneShot() const override { + return state_ == PollSourceEnableState::Oneshot; + } + + template + auto trigger(Args &&...args) { + auto ref = this->watch(); + if (isOneShot()) { + setEnabled(false); + } + auto result = callback_(this, std::forward(args)...); + if (ref.isValid()) { + if (!result) { + setEnabled(false); + } + } + return result; + } + +private: + void setState(PollSourceEnableState state) { state_ = state; } + PollSourceEnableState state_; + C callback_; +}; + +class PollEventSourceIO : public PollEventBase, + public IntrusiveListNode { +public: + PollEventSourceIO(int fd, IOEventFlags flags, IOCallback callback) + : PollEventBase(PollSourceEnableState::Enabled, std::move(callback)), + fd_(fd), flags_(flags) {} + + int fd() const override { return fd_; } + + void setFd(int fd) override { fd_ = fd; } + + IOEventFlags events() const override { return flags_; } + + void setEvents(IOEventFlags flags) override { flags_ = flags; } + + IOEventFlags revents() const override { return revents_; } + +private: + int fd_; + IOEventFlags flags_; + IOEventFlags revents_; +}; + +class PollEventSourceTime : public PollEventBase, + public IntrusiveListNode { +public: + PollEventSourceTime(clockid_t clock, uint64_t time, uint64_t accuracy, + TimeCallback callback) + : PollEventBase(PollSourceEnableState::Oneshot, std::move(callback)), + clock_(clock), time_(time), accuracy_(accuracy) {} + + uint64_t time() const override { return time_; } + void setTime(uint64_t time) override { time_ = time; } + virtual uint64_t accuracy() const override { return accuracy_; } + virtual void setAccuracy(uint64_t accuracy) override { + accuracy_ = accuracy; + } + FCITX_NODISCARD virtual clockid_t clock() const override { return clock_; } + +private: + clockid_t clock_; + uint64_t time_, accuracy_; +}; + +class PollEventSource : public PollEventBase, + public IntrusiveListNode { +public: + PollEventSource(PollSourceEnableState state, EventCallback callback) + : PollEventBase(state, std::move(callback)) {} +}; + +short IOEventFlagsToPoll(IOEventFlags flags) { + short result = 0; + if (flags.test(IOEventFlag::In)) { + result |= POLLIN; + } + if (flags.test(IOEventFlag::Out)) { + result |= POLLOUT; + } + return result; +} + +IOEventFlags PollToIOEventFlags(short revent) { + IOEventFlags result; + if (revent & POLLIN) { + result |= IOEventFlag::In; + } + if (revent & POLLOUT) { + result |= IOEventFlag::Out; + } + if (revent & POLLERR) { + result |= IOEventFlag::Err; + } + return result; +} + +class PollEventLoop : public EventLoopInterface { +public: + bool exec() override { + exit_ = false; + std::vector fds; + while (!exit_) { + bool timeTriggered = false; + bool hasTime = false; + uint64_t timeout = std::numeric_limits::max(); + for (PollEventSourceTime &time : timeEvents_) { + if (time.isEnabled()) { + hasTime = true; + auto current = now(time.clock()); + if (time.time() >= current) { + timeout = std::min(timeout, time.time() - current); + } else { + time.trigger(current); + timeTriggered = true; + break; + } + } + } + + if (exit_) { + break; + } + + if (timeTriggered) { + continue; + } + + timeout /= 1000; + + fds.clear(); + fds.reserve(ioEvents_.size()); + for (PollEventSourceIO &event : ioEvents_) { + if (event.isEnabled()) { + auto pollevent = IOEventFlagsToPoll(event.events()); + if (pollevent) { + fds.push_back(pollfd{.fd = event.fd(), + .events = pollevent, + .revents = 0}); + } + } + } + + if (fds.size() == 0 && !hasTime) { + break; + } + + auto r = poll(fds.data(), fds.size(), timeout); + if (r < 0) { + return false; + } + if (r == 0) { + continue; + } + for (const auto &f : fds) { + if (f.revents) { + for (PollEventSourceIO &event : ioEvents_) { + if (event.fd() == f.fd && event.isEnabled()) { + event.trigger(event.fd(), + PollToIOEventFlags(f.revents)); + } + } + } + } + for (PollEventSource &event : postEvents_) { + if (event.isEnabled()) { + event.trigger(); + } + } + } + for (PollEventSource &event : exitEvents_) { + if (event.isEnabled()) { + event.trigger(); + } + } + + return false; + } + void exit() override { exit_ = true; } + + const char *implementation() const override { return "poll"; } + + void *nativeHandle() override { return nullptr; } + + std::unique_ptr addIOEvent(int fd, IOEventFlags flags, + IOCallback callback) override { + auto event = + std::make_unique(fd, flags, std::move(callback)); + ioEvents_.push_back(*event); + return event; + } + std::unique_ptr + addTimeEvent(clockid_t clock, uint64_t usec, uint64_t accuracy, + TimeCallback callback) override { + auto event = std::make_unique( + clock, usec, accuracy, std::move(callback)); + timeEvents_.push_back(*event); + return event; + } + std::unique_ptr + addDeferEvent(EventCallback callback) override { + return addTimeEvent( + CLOCK_MONOTONIC, 0, 0, + [callback = std::move(callback)](EventSourceTime *event, + uint64_t /*usec*/) -> bool { + return callback(event); + }); + } + std::unique_ptr addPostEvent(EventCallback callback) override { + auto event = std::make_unique( + PollSourceEnableState::Enabled, std::move(callback)); + postEvents_.push_back(*event); + return event; + } + std::unique_ptr addExitEvent(EventCallback callback) override { + auto event = std::make_unique( + PollSourceEnableState::Enabled, std::move(callback)); + exitEvents_.push_back(*event); + return event; + } + +private: + IntrusiveList ioEvents_; + IntrusiveList timeEvents_; + IntrusiveList postEvents_; + IntrusiveList exitEvents_; + bool exit_ = false; +}; + +std::unique_ptr pollEventLoopFactory() { + return std::make_unique(); +} + +int main() { + EventLoop::setEventLoopFactory(pollEventLoopFactory); + runAllEventLoopTests(); + return 0; +} diff --git a/test/testevent.cpp b/test/testevent.cpp index 6750bdff9..35384a819 100644 --- a/test/testevent.cpp +++ b/test/testevent.cpp @@ -1,186 +1,12 @@ /* - * SPDX-FileCopyrightText: 2015-2015 CSSlayer + * SPDX-FileCopyrightText: 2015-2024 CSSlayer * * SPDX-License-Identifier: LGPL-2.1-or-later * */ - -#include -#include -#include -#include -#include "fcitx-utils/eventdispatcher.h" -#include "fcitx-utils/log.h" - -using namespace fcitx; - -void test_basic() { - EventLoop e; - - int pipefd[2]; - int r = pipe(pipefd); - FCITX_ASSERT(r == 0); - - std::unique_ptr source( - e.addIOEvent(pipefd[0], IOEventFlag::In, - [&e, pipefd](EventSource *, int fd, IOEventFlags flags) { - FCITX_ASSERT(pipefd[0] == fd); - if (flags & IOEventFlag::Hup) { - e.exit(); - } - - if (flags & IOEventFlag::In) { - char buf[20]; - auto size = read(fd, buf, 20); - if (size == 0) { - e.exit(); - } else { - FCITX_INFO() << "QUIT" << flags; - FCITX_ASSERT(size == 1); - FCITX_ASSERT(buf[0] == 'a'); - } - } - return true; - })); - - std::unique_ptr source4(e.addDeferEvent([](EventSource *) { - FCITX_INFO() << "DEFER"; - return true; - })); - - std::unique_ptr source5(e.addExitEvent([](EventSource *) { - FCITX_INFO() << "EXIT"; - return true; - })); - - int times = 10; - std::unique_ptr sourceX( - e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 1000000ul, 1, - [×](EventSource *source, uint64_t) { - FCITX_INFO() << "Recur:" << times; - times--; - if (times < 0) { - source->setEnabled(false); - } - return true; - })); - sourceX->setEnabled(true); - - int times2 = 10; - std::unique_ptr sourceX2( - e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 1000000ul, 1, - [×2](EventSourceTime *source, uint64_t t) { - FCITX_INFO() << "Recur 2:" << times2 << " " << t; - times2--; - if (times2 > 0) { - source->setNextInterval(100000); - source->setOneShot(); - } - return true; - })); - - std::unique_ptr source2( - e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 1000000ul, 0, - [pipefd](EventSource *, uint64_t) { - FCITX_INFO() << "WRITE"; - auto r = write(pipefd[1], "a", 1); - FCITX_ASSERT(r == 1); - return true; - })); - - std::unique_ptr source3( - e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 2000000ul, 0, - [pipefd](EventSource *, uint64_t) { - FCITX_INFO() << "CLOSE"; - close(pipefd[1]); - return true; - })); - - e.exec(); -} - -void test_source_deleted() { - EventLoop e; - - int pipefd[2]; - int r = pipe(pipefd); - FCITX_ASSERT(r == 0); - - std::unique_ptr source( - e.addIOEvent(pipefd[0], IOEventFlag::In, - [&source](EventSource *, int, IOEventFlags flags) { - if (flags & IOEventFlag::In) { - FCITX_INFO() << "RESET"; - source.reset(); - } - return true; - })); - - std::unique_ptr source2( - e.addDeferEvent([pipefd](EventSource *) { - FCITX_INFO() << "WRITE"; - auto r = write(pipefd[1], "a", 1); - FCITX_ASSERT(r == 1); - return true; - })); - - std::unique_ptr source3( - e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 2000000ul, 0, - [&e](EventSource *, uint64_t) { - FCITX_INFO() << "EXIT"; - e.exit(); - return true; - })); - - e.exec(); -} - -void test_post_time() { - EventLoop e; - bool ready = false; - auto post = e.addPostEvent([&ready, &e](EventSource *) { - FCITX_INFO() << "POST"; - if (ready) { - e.exit(); - } - return true; - }); - auto time = e.addTimeEvent(CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 1000000, - 0, [&ready](EventSource *, uint64_t) { - FCITX_INFO() << "TIME"; - ready = true; - return true; - }); - e.exec(); -} - -void test_post_io() { - EventLoop e; - EventDispatcher dispatcher; - bool ready = false; - auto post = e.addPostEvent([&ready, &e](EventSource *) { - FCITX_INFO() << "POST"; - if (ready) { - e.exit(); - } - return true; - }); - dispatcher.attach(&e); - std::thread thread([&dispatcher, &ready]() { - sleep(2); - dispatcher.schedule([&ready]() { - FCITX_INFO() << "DISPATCHER"; - ready = true; - }); - }); - e.exec(); - thread.join(); -} +#include "eventlooptests.h" int main() { - test_basic(); - test_source_deleted(); - test_post_time(); - test_post_io(); + runAllEventLoopTests(); return 0; -} +} \ No newline at end of file