Skip to content

Instantly share code, notes, and snippets.

@b-epelbaum
Last active March 7, 2024 18:12
Show Gist options
  • Select an option

  • Save b-epelbaum/088f8862faa1aa8ff7050e18c78c4ac4 to your computer and use it in GitHub Desktop.

Select an option

Save b-epelbaum/088f8862faa1aa8ff7050e18c78c4ac4 to your computer and use it in GitHub Desktop.
Stackable Qt task queue with single timer
#include <functional>
#include <queue>
#include <chrono>
#include <memory>
#include <tuple>
#include <QTimer>
#include <QDateTime>
namespace Helpers
{
class priorityTasker : public QObject
{
Q_OBJECT
class baseTask;
QTimer _timer{};
std::vector<std::shared_ptr<baseTask>> _tasks;
class baseTask
{
public:
baseTask(const int timeout, const bool is_periodic)
: _timeout(timeout),
_is_periodic(is_periodic),
_interval(_timeout),
_expiration_time(QDateTime::currentMSecsSinceEpoch() + _timeout)
{}
baseTask(const baseTask&) = delete;
baseTask(baseTask&&) = delete;
baseTask& operator =(const baseTask&) = delete;
baseTask& operator = (baseTask&&) = delete;
virtual ~baseTask() = default;
virtual void run() = 0;
[[nodiscard]] uint64_t get_expiration_time() const { return _expiration_time; }
void reset_expiration_time()
{
_expiration_time = QDateTime::currentMSecsSinceEpoch() + _interval;
}
int _timeout{};
bool _is_periodic{};
uint64_t _interval{};
uint64_t _expiration_time{};
};
template<typename... Args>
class priorityTask : public baseTask
{
public:
priorityTask(const int timeout, const bool is_periodic, std::function<void(Args...)> callback, Args... args)
: baseTask(timeout, is_periodic)
, _callback(std::move(callback))
, _args(std::make_tuple(std::forward<Args>(args)...))
{}
void run() override
{
std::apply(_callback, _args);
if (_is_periodic)
reset_expiration_time();
else
_timeout = INT_MAX;
}
private:
std::function<void(Args...)> _callback{};
std::tuple<Args...> _args;
};
template<>
class priorityTask<> : public baseTask
{
public:
priorityTask(const int timeout, const bool is_periodic, std::function<void()> callback)
: baseTask(timeout, is_periodic)
, _callback(std::move(callback))
{}
void run() override
{
_callback();
if (_is_periodic)
reset_expiration_time();
else
_timeout = INT_MAX;
}
private:
std::function<void()> _callback;
};
public:
explicit priorityTasker(QObject* parent = nullptr) : QObject(parent)
{
connect(&_timer, &QTimer::timeout, [this]()
{
if (!_tasks.empty())
{
std::ranges::pop_heap(_tasks, compareTask);
const auto task = _tasks.back();
_tasks.pop_back();
task->run();
if (task->_is_periodic)
{
_tasks.push_back(task);
std::ranges::push_heap(_tasks, compareTask);
}
update_timer();
}
});
}
priorityTasker(const priorityTasker&) = delete;
priorityTasker(priorityTasker&&) = delete;
priorityTasker& operator =(const priorityTasker&) = delete;
priorityTasker& operator = (priorityTasker&&) = delete;
~priorityTasker() override = default;
static bool compareTask(const std::shared_ptr<baseTask>& task1, const std::shared_ptr<baseTask>& task2)
{
return task1->get_expiration_time() > task2->get_expiration_time();
}
template<typename... Args>
void add_task(const int timeout, const bool is_periodic, std::function<void(Args...)> callback, Args... args)
{
auto task = std::make_shared<priorityTask<Args...>>
(
timeout,
is_periodic,
std::move(callback),
std::forward<Args>(args)...
);
_tasks.push_back(task);
std::ranges::push_heap(_tasks, compareTask);
update_timer();
}
void add_task(const int timeout, const bool is_periodic, std::function<void()> callback)
{
auto expiration_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout);
const auto task = std::make_shared<priorityTask<>>
(
timeout,
is_periodic,
std::move(callback)
);
_tasks.push_back(task);
std::ranges::push_heap(_tasks, compareTask);
update_timer();
}
private:
void update_timer()
{
_timer.stop();
if (!_tasks.empty())
{
const int64_t current_time = QDateTime::currentMSecsSinceEpoch();
const int64_t next_time = static_cast<int64_t>(_tasks.front()->_expiration_time);
if (const int64_t interval = next_time > current_time ? next_time - current_time : 0; interval > 0)
_timer.start(static_cast<int>(interval));
}
else
_timer.stop();
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment