Skip to content

Commit

Permalink
Implement auto invocation of deferred slots
Browse files Browse the repository at this point in the history
Connection invocations are now evaluated automatically in the event
loop. Any time an invocation is enqueued using the event loop's
connection evaluator, the event loop is waked up.

This required a KDUtils-specific ConnectionEvaluator.

When given event loop is destroyed, it sets a reference in the evaluator
to null. It's a defensive measure for the future to prevent misuse of
lingering evaluators in case the user deletes the event loop first.

AbstractEventLoop::waitForEvents is not pure abstract anymore as it
ensures evaluation of slot invocations.

Removed one copypasted comment in android event loop impl.
  • Loading branch information
MiKom committed Jul 9, 2024
1 parent b4dbbc6 commit 2588379
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 22 deletions.
4 changes: 2 additions & 2 deletions cmake/dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ find_package(KDBindings QUIET)
if(NOT TARGET KDAB::KDBindings)
fetchcontent_declare(
KDBindings
GIT_REPOSITORY https://github.com/KDAB/KDBindings.git
GIT_TAG aa3c8ebe25772fe5ab4b4457f712e8486d5c5c00 # v1.1.0-beta.1
GIT_REPOSITORY https://github.com/MiKom/KDBindings.git
GIT_TAG 407ea91a514814e532415ee2253f84b1de5952d6 # v1.1.0-beta.1
USES_TERMINAL_DOWNLOAD YES USES_TERMINAL_UPDATE YES
)
fetchcontent_makeavailable(KDBindings)
Expand Down
1 change: 1 addition & 0 deletions src/KDFoundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ set(SOURCES
object.cpp
postman.cpp
timer.cpp
platform/abstract_platform_event_loop.cpp
)

set(HEADERS
Expand Down
70 changes: 70 additions & 0 deletions src/KDFoundation/platform/abstract_platform_event_loop.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
This file is part of KDUtils.
SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
Author: Miłosz Kosobucki <[email protected]>
SPDX-License-Identifier: MIT
Contact KDAB at <[email protected]> for commercial licensing options.
*/

#include <KDFoundation/platform/abstract_platform_event_loop.h>
#include "abstract_platform_event_loop.h"

namespace KDFoundation {

/**
* KDUtils-specific connection evaluator that wakes up the owning event loop when slot invocation is
* enqueued via the loop's connection evaluator.
*/
class ConnectionEvaluator final : public KDBindings::ConnectionEvaluator
{
public:
ConnectionEvaluator(AbstractPlatformEventLoop *eventLoop)
: m_eventLoop(eventLoop) { }

void setEventLoop(AbstractPlatformEventLoop *eventLoop)
{
this->m_eventLoop = eventLoop;
}

protected:
void onSlotInvocationEnqueued() override
{
if (m_eventLoop) {
m_eventLoop->wakeUp();
}
}

private:
AbstractPlatformEventLoop *m_eventLoop = nullptr;
};
} // namespace KDFoundation

using namespace KDFoundation;

KDFoundation::AbstractPlatformEventLoop::AbstractPlatformEventLoop()
: m_connectionEvaluator(new ConnectionEvaluator(this))
{
}

AbstractPlatformEventLoop::~AbstractPlatformEventLoop()
{
// Make sure that any remaining references to our connection evaluator aren't referring to a dead
// event loop object.
if (m_connectionEvaluator) {
std::static_pointer_cast<KDFoundation::ConnectionEvaluator>(m_connectionEvaluator)->setEventLoop(nullptr);
}
}

void KDFoundation::AbstractPlatformEventLoop::waitForEvents(int timeout)
{
waitForEventsImpl(timeout);

// Possibly we woke up because of deferred slot invocation was posted. Let's (possibly)
// execute them while we're at it.
if (m_connectionEvaluator) {
m_connectionEvaluator->evaluateDeferredConnections();
}
}
8 changes: 5 additions & 3 deletions src/KDFoundation/platform/abstract_platform_event_loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class Timer;
class KDFOUNDATION_API AbstractPlatformEventLoop
{
public:
virtual ~AbstractPlatformEventLoop() { }
AbstractPlatformEventLoop();
virtual ~AbstractPlatformEventLoop();

void setPostman(Postman *postman) { m_postman = postman; }
Postman *postman() { return m_postman; }
Expand All @@ -37,7 +38,7 @@ class KDFOUNDATION_API AbstractPlatformEventLoop
// -1 means wait forever
// 0 means do not wait (i.e. poll)
// +ve number, wait for up to timeout msecs
virtual void waitForEvents(int timeout) = 0;
virtual void waitForEvents(int timeout);

// Kick the event loop out of waiting
virtual void wakeUp() = 0;
Expand All @@ -56,9 +57,10 @@ class KDFOUNDATION_API AbstractPlatformEventLoop

protected:
virtual std::unique_ptr<AbstractPlatformTimer> createPlatformTimerImpl(Timer *timer) = 0;
virtual void waitForEventsImpl(int timeout) = 0;

Postman *m_postman{ nullptr };
std::shared_ptr<KDBindings::ConnectionEvaluator> m_connectionEvaluator{ new KDBindings::ConnectionEvaluator };
std::shared_ptr<KDBindings::ConnectionEvaluator> m_connectionEvaluator;
};

} // namespace KDFoundation
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ LinuxPlatformEventLoop::~LinuxPlatformEventLoop()
SPDLOG_CRITICAL("Failed to cleanup epoll");
}

void LinuxPlatformEventLoop::waitForEvents(int timeout)
void LinuxPlatformEventLoop::waitForEventsImpl(int timeout)
{
const int maxEventCount = 16;
epoll_event events[maxEventCount];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class KDFOUNDATION_API LinuxPlatformEventLoop : public AbstractPlatformEventLoop
LinuxPlatformEventLoop(LinuxPlatformEventLoop &&other) = delete;
LinuxPlatformEventLoop &operator=(LinuxPlatformEventLoop &&other) = delete;

void waitForEvents(int timeout) override;
void wakeUp() override;

bool registerNotifier(FileDescriptorNotifier *notifier) override;
Expand All @@ -53,6 +52,7 @@ class KDFOUNDATION_API LinuxPlatformEventLoop : public AbstractPlatformEventLoop
int epollEventFromNotifierTypes(bool read, bool write, bool exception);

private:
void waitForEventsImpl(int timeout) override;
std::unique_ptr<AbstractPlatformTimer> createPlatformTimerImpl(Timer *timer) override;

int m_epollHandle = -1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class KDFOUNDATION_API MacOSPlatformEventLoop : public AbstractPlatformEventLoop
MacOSPlatformEventLoop(MacOSPlatformEventLoop &&other) = delete;
MacOSPlatformEventLoop &operator=(MacOSPlatformEventLoop &&other) = delete;

void waitForEvents(int timeout) override;
void wakeUp() override;

bool registerNotifier(FileDescriptorNotifier *notifier) override;
Expand All @@ -36,6 +35,7 @@ class KDFOUNDATION_API MacOSPlatformEventLoop : public AbstractPlatformEventLoop
static void postEmptyEvent();

private:
void waitForEventsImpl(int timeout) override;
std::unique_ptr<AbstractPlatformTimer> createPlatformTimerImpl(Timer *timer) override;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

MacOSPlatformEventLoop::~MacOSPlatformEventLoop() = default;

void MacOSPlatformEventLoop::waitForEvents(int timeout)
void MacOSPlatformEventLoop::waitForEventsImpl(int timeout)
{
@autoreleasepool {
NSDate *expiration = [timeout] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Win32PlatformEventLoop::~Win32PlatformEventLoop()
SPDLOG_WARN("Failed to unregister message window class");
}

void Win32PlatformEventLoop::waitForEvents(int timeout)
void Win32PlatformEventLoop::waitForEventsImpl(int timeout)
{
MSG msg;
bool hasMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
Expand Down
3 changes: 1 addition & 2 deletions src/KDFoundation/platform/win32/win32_platform_event_loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ class KDFOUNDATION_API Win32PlatformEventLoop : public AbstractPlatformEventLoop
Win32PlatformEventLoop(Win32PlatformEventLoop &&other) = delete;
Win32PlatformEventLoop &operator=(Win32PlatformEventLoop &&other) = delete;

void waitForEvents(int timeout) override;

void wakeUp() override;

bool registerNotifier(FileDescriptorNotifier *notifier) override;
Expand All @@ -45,6 +43,7 @@ class KDFOUNDATION_API Win32PlatformEventLoop : public AbstractPlatformEventLoop
std::unordered_map<uintptr_t, Win32PlatformTimer *> timers;

private:
void waitForEventsImpl(int timeout) override;
std::unique_ptr<AbstractPlatformTimer> createPlatformTimerImpl(Timer *timer) override;

struct NotifierSet {
Expand Down
2 changes: 1 addition & 1 deletion src/KDGui/platform/android/android_platform_event_loop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ AndroidPlatformEventLoop::AndroidPlatformEventLoop(AndroidPlatformIntegration *a
m_androidPlatformInitegration = androidPlatformInitegration;
}

void AndroidPlatformEventLoop::waitForEvents(int timeout)
void AndroidPlatformEventLoop::waitForEventsImpl(int timeout)
{
android_poll_source *source;
if (ALooper_pollAll(timeout, nullptr, nullptr, (void **)&source) >= 0) {
Expand Down
7 changes: 1 addition & 6 deletions src/KDGui/platform/android/android_platform_event_loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ class KDGUI_API AndroidPlatformEventLoop : public KDFoundation::AbstractPlatform
public:
AndroidPlatformEventLoop(AndroidPlatformIntegration *androidPlatformInitegration);

// timeout in msecs
// -1 means wait forever
// 0 means do not wait (i.e. poll)
// +ve number, wait for up to timeout msecs
void waitForEvents(int timeout) override;

// Kick the event loop out of waiting
void wakeUp() override;

Expand All @@ -52,6 +46,7 @@ class KDGUI_API AndroidPlatformEventLoop : public KDFoundation::AbstractPlatform
AndroidPlatformIntegration *androidPlatformIntegration();

protected:
void waitForEventsImpl(int timeout) override;
std::unique_ptr<KDFoundation::AbstractPlatformTimer> createPlatformTimerImpl(KDFoundation::Timer *timer) final;

private:
Expand Down
34 changes: 31 additions & 3 deletions tests/auto/utils/signal/tst_signal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,39 @@ TEST_SUITE("Signal")
signal2.emit(3);
CHECK(val == 4); // val not changing immediately after emit

app.connectionEvaluator()->evaluateDeferredConnections();
app.processEvents();

CHECK(val == 9);
}

TEST_CASE("Emit from another thread")
{
KDFoundation::CoreApplication app;
int mainThreadCounter = 0;
int secondaryThreadCounter = 0;
KDBindings::Signal<> signal;

std::thread t1([&] {
auto conn = signal.connectDeferred(app.connectionEvaluator(), [&] {
++mainThreadCounter;
});
conn = signal.connect([&] {
++secondaryThreadCounter;
});
signal.emit();
assert(secondaryThreadCounter == 1);
});

t1.join();

REQUIRE(secondaryThreadCounter == 1);
REQUIRE(mainThreadCounter == 0);

app.processEvents();

REQUIRE(mainThreadCounter == 1);
}

TEST_CASE("Emit Multiple Signals with Evaluator")
{
KDFoundation::CoreApplication app;
Expand Down Expand Up @@ -85,7 +113,7 @@ TEST_SUITE("Signal")
CHECK(val1 == 4);
CHECK(val2 == 4);

app.connectionEvaluator()->evaluateDeferredConnections();
app.processEvents();

CHECK(val1 == 6);
CHECK(val2 == 7);
Expand All @@ -108,7 +136,7 @@ TEST_SUITE("Signal")

connection.disconnect();

app.connectionEvaluator()->evaluateDeferredConnections();
app.processEvents();

CHECK(val == 4);
}
Expand Down

0 comments on commit 2588379

Please sign in to comment.