Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. I implemented a debounce connnect to prevent sending signals too frequently, please help to check whether this code will cause bugs.
Qt 6.11 is out! See what's new in the release blog

I implemented a debounce connnect to prevent sending signals too frequently, please help to check whether this code will cause bugs.

Scheduled Pinned Locked Moved Unsolved General and Desktop
4 Posts 3 Posters 1.4k Views 4 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • J Offline
    J Offline
    John Van
    wrote on last edited by
    #1
    #include <QTimer>
    #include <type_traits>
    #include <tuple>
    class Debounce
    {
    	template <typename T>
    	struct lambda_traits : lambda_traits<decltype(&T::operator())> {};
    
    	template <typename ClassType, typename ReturnType, typename... Args>
    	struct lambda_traits<ReturnType(ClassType::*)(Args...) const>
    	{
    		static const std::size_t arity = sizeof...(Args);
    	};
    
    	template<typename Func, std::size_t... Is, typename... Args>
    	void static call_slot(Func slot, std::index_sequence<Is...>, Args&&... args) {
    		slot(std::get<Is>(std::forward_as_tuple(std::forward<Args>(args)...))...);
    	}
    public:
    	template <typename Func1, typename Func2>
    	static inline QMetaObject::Connection
    		connect(const typename QtPrivate::FunctionPointer<Func1>::Object* sender, Func1 signal, const QObject* context, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection)
    	{
    		QTimer* timer = new QTimer((QObject*)sender);
    		timer->setSingleShot(true);
    		auto func3 = new std::function<void()>;
    		QObject::connect(sender, signal, timer, [=](auto&&... args) {*func3 =
    			[=]() {call_slot(slot, std::make_index_sequence<lambda_traits<Func2>::arity>(), (args)...);};
    			timer->start(200); });
    		return QObject::connect(timer, &QTimer::timeout, context, [=]() {(*func3)(); }, type);
    	}
    };
    #include <QApplication>
    #include <QSlider>
    #include <QDoubleSpinBox>
    
    int main(int argc, char* argv[])
    {
    	QApplication app(argc, argv);
    	auto w = new QWidget();
    	auto sl = new QSlider(Qt::Horizontal, w);
    	auto dsp = new QDoubleSpinBox(w);
    	w->show();
    	sl->move(30, 30);
    	w->resize(200, 100);
    
    	Debounce::connect(sl, &QSlider::valueChanged, sl, []() {printf("QSlider1 \n "); });
    	Debounce::connect(sl, &QSlider::valueChanged, sl, [](int v) {printf("QSlider2  %d  \n", v); });
    	Debounce::connect(dsp, QOverload<double>::of(&QDoubleSpinBox::valueChanged), dsp, [=](double v) {printf("QDoubleSpinBox %f  \n", v);  emit sl->rangeChanged(0, v); });
    	Debounce::connect(sl, &QSlider::rangeChanged, sl, [](int v0, int v1) {printf("rangeChanged   %d  %d\n", v0, v1); });
    	return app.exec();
    }
    
    1 Reply Last reply
    0
    • D Offline
      D Offline
      DerReisende
      wrote on last edited by
      #2

      I haven’t had a look at your code.
      But I am using KDSignalThrottler for signal debouncing. Works great so far and maybe it is of use for you.

      1 Reply Last reply
      1
      • S Offline
        S Offline
        SimonSchroeder
        wrote on last edited by
        #3

        This will most likely fully debounce your signals. You should think about this if this is actually what you want. If there is no pause of at least 200ms between emitted signals, the slot will never be executed. A common solution is to use a second timer with a longer timeout which will not be restarted for every signal emitted, but only if it is not running. On timeout, the second timer will also stop your current timer, such that the slot is not executed twice. It is up to you, which kind of debounce you want.

        J 1 Reply Last reply
        0
        • S SimonSchroeder

          This will most likely fully debounce your signals. You should think about this if this is actually what you want. If there is no pause of at least 200ms between emitted signals, the slot will never be executed. A common solution is to use a second timer with a longer timeout which will not be restarted for every signal emitted, but only if it is not running. On timeout, the second timer will also stop your current timer, such that the slot is not executed twice. It is up to you, which kind of debounce you want.

          J Offline
          J Offline
          John Van
          wrote on last edited by
          #4

          @SimonSchroeder I added the Throttle class to implement low-frequency send signals

          #include <type_traits>
          #include <tuple>
          #include <QTimer>
          #include <QCoreApplication>
          namespace QtUtility
          {
          	template <typename T>
          	struct lambda_traits : lambda_traits<decltype(&T::operator())> {};
          
          	template <typename ClassType, typename ReturnType, typename... Args>
          	struct lambda_traits<ReturnType(ClassType::*)(Args...) const>
          	{
          		static const std::size_t arity = sizeof...(Args);
          	};
          
          	template<typename Func, std::size_t... Is, typename... Args>
          	void static call_slot(Func slot, std::index_sequence<Is...>, Args&&... args) {
          		slot(std::get<Is>(std::forward_as_tuple(std::forward<Args>(args)...))...);
          	}
          	class MyTimer : public QTimer
          	{
          	public:
          		using QTimer::QTimer;
          		std::function<void()> func;
          	};
          }
          class Debounce : QObject
          {
          public:
          	int interval = 200;
          	Debounce(int v, QObject* parent = NULL)
          	{
          		interval = v;
          		setParent(parent ? parent : QCoreApplication::instance());
          	}
          	template <typename Func1, typename Func2>
          	inline QMetaObject::Connection
          		connect(const typename QtPrivate::FunctionPointer<Func1>::Object* sender, Func1 signal, const QObject* context, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection)
          	{
          		using namespace QtUtility;
          		auto timer = new MyTimer((QObject*)sender);
          		timer->setSingleShot(true);
          		QObject::connect(sender, signal, timer, [=](auto&&... args) {timer->func =
          			[=]() {call_slot(slot, std::make_index_sequence<lambda_traits<Func2>::arity>(), (args)...); };
          		timer->start(interval); });
          		return QObject::connect(timer, &QTimer::timeout, context, [=]() {timer->func(); }, type);
          	}
          };
          class Throttle : QObject
          {
          public:
          	int interval = 200;
          	Throttle(int v, QObject* parent = NULL)
          	{
          		interval = v;
          		setParent(parent ? parent : QCoreApplication::instance());
          	}
          	template <typename Func1, typename Func2>
          	inline QMetaObject::Connection
          		connect(const typename QtPrivate::FunctionPointer<Func1>::Object* sender, Func1 signal, const QObject* context, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection)
          	{
          		using namespace QtUtility;
          		auto timer = new MyTimer((QObject*)sender);
          		timer->setSingleShot(true);
          		QObject::connect(sender, signal, timer, [=](auto&&... args) {timer->func =
          			[=]() {call_slot(slot, std::make_index_sequence<lambda_traits<Func2>::arity>(), (args)...); };
          		if (!timer->isActive())
          			timer->start(interval); });
          		return QObject::connect(timer, &QTimer::timeout, context, [=]() {timer->func(); }, type);
          	}
          };
          #include <QApplication>
          #include <QSlider>
          #include <QDoubleSpinBox>
          #include <QWheelEvent>
          int main(int argc, char* argv[])
          {
          	QApplication app(argc, argv);
          	auto w = new QWidget();
          	auto sl = new QSlider(Qt::Horizontal, w);
          	auto dsp = new QDoubleSpinBox(w);
          	w->show();
          	sl->move(30, 30);
          	w->resize(200, 100);
          	{
          		auto db = new Debounce(200);
          		auto thr = new Throttle(200);
          		QObject::connect(dsp, QOverload<double>::of(&QDoubleSpinBox::valueChanged), dsp, [=](double v) {
          			emit sl->rangeChanged(0, v); });
          		db->connect(dsp, QOverload<double>::of(&QDoubleSpinBox::valueChanged), dsp, [=](double v) {
          			printf("QDoubleSpinBox %f  \n", v); });
          		db->connect(sl, &QSlider::valueChanged, sl, []() {printf("QSlider1 \n "); });
          		db->connect(sl, &QSlider::valueChanged, sl, [](int v) {printf("QSlider2  %d  \n", v); });
          		thr->connect(sl, &QSlider::rangeChanged, sl, [](int v0, int v1) {printf("rangeChanged   %d  %d\n", v0, v1); });
          	}
          	return app.exec();
          }
          
          1 Reply Last reply
          0

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved