Просмотр исходного кода

LibCore: Port EventLoop to Windows

stasoid 9 месяцев назад
Родитель
Сommit
1c77135948

+ 6 - 3
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)

+ 1 - 1
Libraries/LibCore/EventLoop.cpp

@@ -9,7 +9,7 @@
 #include <AK/Badge.h>
 #include <LibCore/Event.h>
 #include <LibCore/EventLoop.h>
-#include <LibCore/EventLoopImplementationUnix.h>
+#include <LibCore/EventLoopImplementation.h>
 #include <LibCore/EventReceiver.h>
 #include <LibCore/Promise.h>
 #include <LibCore/ThreadEventQueue.h>

+ 6 - 2
Libraries/LibCore/EventLoopImplementation.cpp

@@ -7,8 +7,12 @@
 #include <AK/NonnullOwnPtr.h>
 #include <LibCore/Event.h>
 #include <LibCore/EventLoopImplementation.h>
-#include <LibCore/EventLoopImplementationUnix.h>
 #include <LibCore/ThreadEventQueue.h>
+#ifdef AK_OS_WINDOWS
+#    include <LibCore/EventLoopImplementationWindows.h>
+#else
+#    include <LibCore/EventLoopImplementationUnix.h>
+#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;
 }
 

+ 2 - 0
Libraries/LibCore/EventLoopImplementationUnix.h

@@ -62,4 +62,6 @@ private:
     Array<int, 2>& m_wake_pipe_fds;
 };
 
+using EventLoopManagerPlatform = EventLoopManagerUnix;
+
 }

+ 244 - 0
Libraries/LibCore/EventLoopImplementationWindows.cpp

@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
+ * Copyright (c) 2024, stasoid <stasoid@yahoo.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibCore/EventLoopImplementationWindows.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/ThreadEventQueue.h>
+#include <WinSock2.h>
+#include <io.h>
+
+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<Handle> : DefaultTraits<Handle> {
+    static unsigned hash(Handle const& h) { return Traits<HANDLE>::hash(h.handle); }
+};
+template<>
+constexpr bool IsHashCompatible<HANDLE, Handle> = true;
+
+namespace Core {
+
+struct EventLoopTimer {
+    WeakPtr<EventReceiver> owner;
+    TimerShouldFireWhenNotVisible fire_when_not_visible = TimerShouldFireWhenNotVisible::No;
+};
+
+struct ThreadData {
+    static ThreadData& the()
+    {
+        thread_local OwnPtr<ThreadData> thread_data = make<ThreadData>();
+        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<Handle, EventLoopTimer> timers;
+    HashMap<Handle, Notifier*> 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<NotifierActivationEvent>(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<TimerEvent>());
+        }
+    }
+
+    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>&& 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), &notifier);
+}
+
+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 == &notifier; });
+}
+
+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<void(int)> 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<EventLoopImplementation> EventLoopManagerWindows::make_implementation()
+{
+    return make<EventLoopImplementationWindows>();
+}
+
+}

+ 60 - 0
Libraries/LibCore/EventLoopImplementationWindows.h

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtr.h>
+#include <LibCore/EventLoopImplementation.h>
+
+namespace Core {
+
+class EventLoopManagerWindows final : public EventLoopManager {
+public:
+    virtual ~EventLoopManagerWindows() override = default;
+
+    virtual NonnullOwnPtr<EventLoopImplementation> 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<void(int)> handler) override;
+    virtual void unregister_signal(int handler_id) override;
+};
+
+class EventLoopImplementationWindows final : public EventLoopImplementation {
+public:
+    static NonnullOwnPtr<EventLoopImplementationWindows> create() { return make<EventLoopImplementationWindows>(); }
+
+    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<Event>&&) 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;
+
+}

+ 1 - 0
Libraries/LibCore/Notifier.h

@@ -9,6 +9,7 @@
 #include <AK/Function.h>
 #include <LibCore/Event.h>
 #include <LibCore/EventReceiver.h>
+#include <pthread.h>
 
 namespace Core {