Skip to content

Commit

Permalink
Add facilities for signal throttling and debouncing
Browse files Browse the repository at this point in the history
  • Loading branch information
phyBrackets committed Oct 13, 2023
1 parent 6f17576 commit 6d1fece
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 0 deletions.
61 changes: 61 additions & 0 deletions src/kdbindings/signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
#pragma once

#include <assert.h>
#include <chrono>
#include <functional>
#include <memory>
#include <optional>
#include <stdexcept>
#include <thread>
#include <type_traits>
#include <utility>

Expand Down Expand Up @@ -245,6 +247,49 @@ class Signal
return m_connections.insert({ slot });
}

// Connect a callback function with Throttling
Private::GenerationalIndex connectWithThrottling(std::function<void(Args...)> 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<void(Args...)> 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<std::chrono::milliseconds>(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
{
Expand Down Expand Up @@ -393,6 +438,22 @@ class Signal
return connect(bound);
}

// Connect a callback function and return a connection handle with throttling
ConnectionHandle connectWithThrottling(std::function<void(Args...)> 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<void(Args...)> const &slot, int interval)
{
ensureImpl();

return ConnectionHandle{ m_impl, m_impl->connectWithDebouncing(slot, interval) };
}

/**
* Disconnect a previously connected slot.
*
Expand Down
30 changes: 30 additions & 0 deletions tests/signal/tst_signal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,36 @@ TEST_CASE("Signal connections")
REQUIRE(lambdaCalled == true);
}

SUBCASE("Test connectWithThrottling")
{
Signal<int> 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<int> 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<std::string, int> signal;
Expand Down

0 comments on commit 6d1fece

Please sign in to comment.