From 6d1fecec0ebdc77156cef43ab37ba057b116eea1 Mon Sep 17 00:00:00 2001 From: Shivam Kunwar Date: Fri, 13 Oct 2023 22:02:11 +0530 Subject: [PATCH] Add facilities for signal throttling and debouncing --- src/kdbindings/signal.h | 61 +++++++++++++++++++++++++++++++++++++ tests/signal/tst_signal.cpp | 30 ++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/kdbindings/signal.h b/src/kdbindings/signal.h index 87d0a7c..21c871f 100644 --- a/src/kdbindings/signal.h +++ b/src/kdbindings/signal.h @@ -12,10 +12,12 @@ #pragma once #include +#include #include #include #include #include +#include #include #include @@ -245,6 +247,49 @@ class Signal return m_connections.insert({ slot }); } + // Connect a callback function with Throttling + Private::GenerationalIndex connectWithThrottling(std::function const &slot, int interval) + { + bool shouldWait = false; + std::chrono::milliseconds throttleDelay(interval); + + auto throttleCallBack = [slot, &shouldWait, throttleDelay](Args... args) mutable { + if (shouldWait) + return; + + slot(args...); + + shouldWait = true; + std::thread([&shouldWait, throttleDelay]() { + std::this_thread::sleep_for(throttleDelay); + + shouldWait = false; + }).join(); + }; + return m_connections.insert({ throttleCallBack }); + } + + // Connect a callback function with Debouncing + Private::GenerationalIndex connectWithDebouncing(std::function const &slot, int interval) + { + bool debouncing = false; + + std::chrono::milliseconds debounceDelay(interval); + auto timer = std::chrono::high_resolution_clock::now(); + + auto debounceCallBack = [slot, debounceDelay, timer](Args... args) mutable { + auto now = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(now - timer); + + if (elapsed.count() >= debounceDelay.count()) { + slot(args...); + timer = now; + } + }; + + return m_connections.insert({ debounceCallBack }); + } + // Disconnects a previously connected function void disconnect(const Private::GenerationalIndex &id) override { @@ -393,6 +438,22 @@ class Signal return connect(bound); } + // Connect a callback function and return a connection handle with throttling + ConnectionHandle connectWithThrottling(std::function const &slot, int interval) + { + ensureImpl(); + + return ConnectionHandle{ m_impl, m_impl->connectWithThrottling(slot, interval) }; + } + + // Connect a callback function and return a connection handle with debouncing + ConnectionHandle connectWithDebouncing(std::function const &slot, int interval) + { + ensureImpl(); + + return ConnectionHandle{ m_impl, m_impl->connectWithDebouncing(slot, interval) }; + } + /** * Disconnect a previously connected slot. * diff --git a/tests/signal/tst_signal.cpp b/tests/signal/tst_signal.cpp index 683b6c8..01f3ee2 100644 --- a/tests/signal/tst_signal.cpp +++ b/tests/signal/tst_signal.cpp @@ -107,6 +107,36 @@ TEST_CASE("Signal connections") REQUIRE(lambdaCalled == true); } + SUBCASE("Test connectWithThrottling") + { + Signal signal; + + int count = 0; + auto handle = signal.connectWithThrottling([&count](int value) { + count++; + }, 100); + + signal.emit(2); + signal.emit(2); + REQUIRE(count == 2); + } + + SUBCASE("Test connectWithDebouncing") + { + Signal signal; + int count = 0; + auto handle = signal.connectWithDebouncing([&count](int value) { + count++; + }, 100); + + signal.emit(1); + REQUIRE(count == 0); // Debouncing interval hasn't passed + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait for debouncing interval + signal.emit(2); + REQUIRE(count == 1); // The function should be called after the pause + } + SUBCASE("A signal with arguments can be connected to a lambda and invoked with const l-value args") { Signal signal;