From 1c7713594864dc123e4ceb395c1d5e61c2b4fea4 Mon Sep 17 00:00:00 2001 From: stasoid Date: Thu, 31 Oct 2024 12:44:19 +0500 Subject: [PATCH] LibCore: Port EventLoop to Windows --- Libraries/LibCore/CMakeLists.txt | 9 +- Libraries/LibCore/EventLoop.cpp | 2 +- Libraries/LibCore/EventLoopImplementation.cpp | 8 +- .../LibCore/EventLoopImplementationUnix.h | 2 + .../EventLoopImplementationWindows.cpp | 244 ++++++++++++++++++ .../LibCore/EventLoopImplementationWindows.h | 60 +++++ Libraries/LibCore/Notifier.h | 1 + 7 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 Libraries/LibCore/EventLoopImplementationWindows.cpp create mode 100644 Libraries/LibCore/EventLoopImplementationWindows.h diff --git a/Libraries/LibCore/CMakeLists.txt b/Libraries/LibCore/CMakeLists.txt index 38a7cb24e25..7465c41290d 100644 --- a/Libraries/LibCore/CMakeLists.txt +++ b/Libraries/LibCore/CMakeLists.txt @@ -36,7 +36,6 @@ set(SOURCES Event.cpp EventLoop.cpp EventLoopImplementation.cpp - EventLoopImplementationUnix.cpp EventReceiver.cpp MappedFile.cpp MimeData.cpp @@ -55,9 +54,13 @@ set(SOURCES ) if (WIN32) - list(APPEND SOURCES AnonymousBufferWindows.cpp) + list(APPEND SOURCES + AnonymousBufferWindows.cpp + EventLoopImplementationWindows.cpp) else() - list(APPEND SOURCES AnonymousBuffer.cpp) + list(APPEND SOURCES + AnonymousBuffer.cpp + EventLoopImplementationUnix.cpp) endif() if (NOT WIN32 AND NOT EMSCRIPTEN) diff --git a/Libraries/LibCore/EventLoop.cpp b/Libraries/LibCore/EventLoop.cpp index 26c3bce127b..e84068eb1ac 100644 --- a/Libraries/LibCore/EventLoop.cpp +++ b/Libraries/LibCore/EventLoop.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Libraries/LibCore/EventLoopImplementation.cpp b/Libraries/LibCore/EventLoopImplementation.cpp index 02ff323a645..9d0defeca93 100644 --- a/Libraries/LibCore/EventLoopImplementation.cpp +++ b/Libraries/LibCore/EventLoopImplementation.cpp @@ -7,8 +7,12 @@ #include #include #include -#include #include +#ifdef AK_OS_WINDOWS +# include +#else +# include +#endif namespace Core { @@ -23,7 +27,7 @@ static EventLoopManager* s_event_loop_manager; EventLoopManager& EventLoopManager::the() { if (!s_event_loop_manager) - s_event_loop_manager = new EventLoopManagerUnix; + s_event_loop_manager = new EventLoopManagerPlatform; return *s_event_loop_manager; } diff --git a/Libraries/LibCore/EventLoopImplementationUnix.h b/Libraries/LibCore/EventLoopImplementationUnix.h index 7d80205ad35..555a83fce7d 100644 --- a/Libraries/LibCore/EventLoopImplementationUnix.h +++ b/Libraries/LibCore/EventLoopImplementationUnix.h @@ -62,4 +62,6 @@ private: Array& m_wake_pipe_fds; }; +using EventLoopManagerPlatform = EventLoopManagerUnix; + } diff --git a/Libraries/LibCore/EventLoopImplementationWindows.cpp b/Libraries/LibCore/EventLoopImplementationWindows.cpp new file mode 100644 index 00000000000..5794be32407 --- /dev/null +++ b/Libraries/LibCore/EventLoopImplementationWindows.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2023, Andreas Kling + * Copyright (c) 2024, stasoid + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +struct Handle { + HANDLE handle = NULL; + + explicit Handle(HANDLE h = NULL) + : handle(h) + { + } + Handle(Handle&& h) + { + handle = h.handle; + h.handle = NULL; + } + void operator=(Handle&& h) + { + VERIFY(!handle); + handle = h.handle; + h.handle = NULL; + } + ~Handle() + { + if (handle) + CloseHandle(handle); + } + + bool operator==(Handle const& h) const { return handle == h.handle; } + bool operator==(HANDLE h) const { return handle == h; } +}; + +template<> +struct Traits : DefaultTraits { + static unsigned hash(Handle const& h) { return Traits::hash(h.handle); } +}; +template<> +constexpr bool IsHashCompatible = true; + +namespace Core { + +struct EventLoopTimer { + WeakPtr owner; + TimerShouldFireWhenNotVisible fire_when_not_visible = TimerShouldFireWhenNotVisible::No; +}; + +struct ThreadData { + static ThreadData& the() + { + thread_local OwnPtr thread_data = make(); + return *thread_data; + } + + ThreadData() + { + wake_event.handle = CreateEvent(NULL, FALSE, FALSE, NULL); + VERIFY(wake_event.handle); + } + + // Each thread has its own timers, notifiers and a wake event. + HashMap timers; + HashMap notifiers; + + // The wake event is used to notify another event loop that someone has called wake(). + Handle wake_event; +}; + +EventLoopImplementationWindows::EventLoopImplementationWindows() + : m_wake_event(ThreadData::the().wake_event.handle) +{ +} + +int EventLoopImplementationWindows::exec() +{ + for (;;) { + if (m_exit_requested) + return m_exit_code; + pump(PumpMode::WaitForEvents); + } + VERIFY_NOT_REACHED(); +} + +size_t EventLoopImplementationWindows::pump(PumpMode) +{ + auto& thread_data = ThreadData::the(); + auto& notifiers = thread_data.notifiers; + auto& timers = thread_data.timers; + + size_t event_count = 1 + notifiers.size() + timers.size(); + // If 64 events limit proves to be insufficient RegisterWaitForSingleObject or other methods + // can be used instead as mentioned in https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects + // TODO: investigate if event_count can realistically exceed 64 + VERIFY(event_count <= MAXIMUM_WAIT_OBJECTS); + + HANDLE event_handles[event_count]; + event_handles[0] = thread_data.wake_event.handle; + + size_t index = 1; + for (auto& entry : notifiers) + event_handles[index++] = entry.key.handle; + for (auto& entry : timers) + event_handles[index++] = entry.key.handle; + + DWORD result = WaitForMultipleObjects(event_count, event_handles, FALSE, INFINITE); + index = result - WAIT_OBJECT_0; + VERIFY(index < event_count); + + if (index != 0) { + if (index <= notifiers.size()) { + Notifier* notifier = *notifiers.get(event_handles[index]); + ThreadEventQueue::current().post_event(*notifier, make(notifier->fd(), notifier->type())); + } else { + auto& timer = *timers.get(event_handles[index]); + if (auto strong_owner = timer.owner.strong_ref()) + if (timer.fire_when_not_visible == TimerShouldFireWhenNotVisible::Yes || strong_owner->is_visible_for_timer_purposes()) + ThreadEventQueue::current().post_event(*strong_owner, make()); + } + } + + return ThreadEventQueue::current().process(); +} + +void EventLoopImplementationWindows::quit(int code) +{ + m_exit_requested = true; + m_exit_code = code; +} + +void EventLoopImplementationWindows::unquit() +{ + m_exit_requested = false; + m_exit_code = 0; +} + +bool EventLoopImplementationWindows::was_exit_requested() const +{ + return m_exit_requested; +} + +void EventLoopImplementationWindows::post_event(EventReceiver& receiver, NonnullOwnPtr&& event) +{ + m_thread_event_queue.post_event(receiver, move(event)); + if (&m_thread_event_queue != &ThreadEventQueue::current()) + wake(); +} + +void EventLoopImplementationWindows::wake() +{ + SetEvent(m_wake_event); +} + +void EventLoopImplementationWindows::notify_forked_and_in_child() +{ + dbgln("Core::EventLoopManagerWindows::notify_forked_and_in_child() is not implemented"); + VERIFY_NOT_REACHED(); +} + +static int notifier_type_to_network_event(NotificationType type) +{ + switch (type) { + case NotificationType::Read: + return FD_READ; + case NotificationType::Write: + return FD_WRITE; + default: + dbgln("This notification type is not implemented: {}", (int)type); + VERIFY_NOT_REACHED(); + } +} + +void EventLoopManagerWindows::register_notifier(Notifier& notifier) +{ + HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL); + VERIFY(event); + SOCKET socket = _get_osfhandle(notifier.fd()); + VERIFY(socket != INVALID_SOCKET); + int rc = WSAEventSelect(socket, event, notifier_type_to_network_event(notifier.type())); + VERIFY(rc == 0); + + auto& notifiers = ThreadData::the().notifiers; + VERIFY(!notifiers.get(event).has_value()); + notifiers.set(Handle(event), ¬ifier); +} + +void EventLoopManagerWindows::unregister_notifier(Notifier& notifier) +{ + // remove_first_matching would be clearer, but currently there is no such method in HashMap + ThreadData::the().notifiers.remove_all_matching([&](auto&, auto value) { return value == ¬ifier; }); +} + +intptr_t EventLoopManagerWindows::register_timer(EventReceiver& object, int milliseconds, bool should_reload, TimerShouldFireWhenNotVisible fire_when_not_visible) +{ + VERIFY(milliseconds >= 0); + HANDLE timer = CreateWaitableTimer(NULL, FALSE, NULL); + VERIFY(timer); + + LARGE_INTEGER first_time = {}; + // Measured in 0.1μs intervals, negative means starting from now + first_time.QuadPart = -10'000 * milliseconds; + BOOL rc = SetWaitableTimer(timer, &first_time, should_reload ? milliseconds : 0, NULL, NULL, FALSE); + VERIFY(rc); + + auto& timers = ThreadData::the().timers; + VERIFY(!timers.get(timer).has_value()); + timers.set(Handle(timer), { object, fire_when_not_visible }); + return (intptr_t)timer; +} + +void EventLoopManagerWindows::unregister_timer(intptr_t timer_id) +{ + ThreadData::the().timers.remove((HANDLE)timer_id); +} + +int EventLoopManagerWindows::register_signal(int signal_number, Function handler) +{ + dbgln("Core::EventLoopManagerWindows::register_signal() is not implemented"); + VERIFY_NOT_REACHED(); +} + +void EventLoopManagerWindows::unregister_signal(int handler_id) +{ + dbgln("Core::EventLoopManagerWindows::unregister_signal() is not implemented"); + VERIFY_NOT_REACHED(); +} + +void EventLoopManagerWindows::did_post_event() +{ +} + +NonnullOwnPtr EventLoopManagerWindows::make_implementation() +{ + return make(); +} + +} diff --git a/Libraries/LibCore/EventLoopImplementationWindows.h b/Libraries/LibCore/EventLoopImplementationWindows.h new file mode 100644 index 00000000000..b3e692d7b24 --- /dev/null +++ b/Libraries/LibCore/EventLoopImplementationWindows.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Core { + +class EventLoopManagerWindows final : public EventLoopManager { +public: + virtual ~EventLoopManagerWindows() override = default; + + virtual NonnullOwnPtr make_implementation() override; + + virtual intptr_t register_timer(EventReceiver&, int milliseconds, bool should_reload, TimerShouldFireWhenNotVisible) override; + virtual void unregister_timer(intptr_t timer_id) override; + + virtual void register_notifier(Notifier&) override; + virtual void unregister_notifier(Notifier&) override; + + virtual void did_post_event() override; + + virtual int register_signal(int signal_number, Function handler) override; + virtual void unregister_signal(int handler_id) override; +}; + +class EventLoopImplementationWindows final : public EventLoopImplementation { +public: + static NonnullOwnPtr create() { return make(); } + + EventLoopImplementationWindows(); + virtual ~EventLoopImplementationWindows() override = default; + + virtual int exec() override; + virtual size_t pump(PumpMode) override; + virtual void quit(int) override; + + virtual void wake() override; + + virtual void unquit() override; + virtual bool was_exit_requested() const override; + virtual void notify_forked_and_in_child() override; + virtual void post_event(EventReceiver& receiver, NonnullOwnPtr&&) override; + +private: + bool m_exit_requested { false }; + int m_exit_code { 0 }; + + // The wake event handle of this event loop needs to be accessible from other threads. + void*& m_wake_event; +}; + +using EventLoopManagerPlatform = EventLoopManagerWindows; + +} diff --git a/Libraries/LibCore/Notifier.h b/Libraries/LibCore/Notifier.h index 4d8c194dec0..e5675b8336e 100644 --- a/Libraries/LibCore/Notifier.h +++ b/Libraries/LibCore/Notifier.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace Core {