31 std::function<void()> callback;
32 std::chrono::steady_clock::time_point posted = std::chrono::steady_clock::now();
33 std::chrono::milliseconds delay;
37 QObject* anchor =
nullptr;
39 std::uint64_t next_id = 0;
40 std::unordered_map<std::uint64_t, std::shared_ptr<Job>> pending;
43 static void close_(
const std::shared_ptr<State>& state,
bool delete_anchor)
noexcept {
44 decltype(state->pending) retired;
46 std::lock_guard lock(state->mutex);
47 state->active =
false;
48 retired.swap(state->pending);
49 if (delete_anchor && state->anchor) {
52 QMetaObject::invokeMethod(state->anchor, &QObject::deleteLater,
53 Qt::QueuedConnection);
54 }
else if (!delete_anchor) {
55 state->anchor =
nullptr;
62 class Anchor final :
public QObject {
64 Anchor(QObject* parent, std::shared_ptr<State> state)
65 : QObject(parent), state_(std::move(state)) {}
66 ~Anchor()
override { close_(state_,
false); }
68 std::shared_ptr<State> state_;
75 explicit QtDispatcher(QObject* context = QCoreApplication::instance())
76 : state_(
std::make_shared<State>()) {
77 if (!context)
throw std::invalid_argument(
"QtDispatcher: context must not be null");
78 if (QThread::currentThread() != context->thread())
79 throw std::logic_error(
"QtDispatcher: construct on the context's owner thread");
80 state_->anchor =
new Anchor(context, state_);
87 void post(std::function<
void()> callback)
override {
88 enqueue_(std::move(callback), std::chrono::milliseconds{0});
95 std::function<
void()> callback)
override {
96 if (delay.count() < 0) delay = std::chrono::milliseconds{0};
97 enqueue_(std::move(callback), delay);
101 std::lock_guard lock(state_->mutex);
102 return state_->active && state_->anchor &&
103 QThread::currentThread() == state_->anchor->thread();
107 return ::aria::SchedulerCaps::Post
114 void enqueue_(std::function<
void()> callback, std::chrono::milliseconds delay) {
115 if (!callback)
return;
118 auto job = std::make_shared<Job>(Job{std::move(callback),
119 std::chrono::steady_clock::now(), delay});
121 std::lock_guard lock(state->mutex);
122 if (!state->active || !state->anchor)
return;
123 if (state->next_id == std::numeric_limits<std::uint64_t>::max())
124 throw std::overflow_error(
"QtDispatcher: task identifier exhausted");
125 const auto id = ++state->next_id;
126 state->pending.emplace(
id, job);
128 if (!QMetaObject::invokeMethod(state->anchor,
129 [weak = std::weak_ptr<State>(state),
id] { deliver_(weak, id); },
130 Qt::QueuedConnection)) {
131 state->pending.erase(
id);
134 state->pending.erase(
id);
139 static void deliver_(
const std::weak_ptr<State>& weak, std::uint64_t
id)
noexcept {
140 auto state = weak.lock();
142 std::shared_ptr<Job> ready;
145 std::chrono::milliseconds remaining{0};
147 std::lock_guard lock(state->mutex);
148 if (!state->active || !state->anchor)
return;
149 auto it = state->pending.find(
id);
150 if (it == state->pending.end())
return;
151 anchor = state->anchor;
152 const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
153 std::chrono::steady_clock::now() - it->second->posted);
154 if (elapsed < it->second->delay) {
155 remaining = it->second->delay - elapsed;
157 ready = std::move(it->second);
158 state->pending.erase(it);
170 auto timer = std::make_unique<QTimer>(anchor);
171 timer->setSingleShot(
true);
172 const auto maximum = std::numeric_limits<int>::max();
173 const int interval = remaining.count() > maximum
174 ? maximum :
static_cast<int>(remaining.count());
175 auto* raw = timer.get();
176 QObject::connect(raw, &QTimer::timeout, anchor, [weak,
id, raw] {
180 timer->start(interval);
181 (void)timer.release();
183 std::shared_ptr<Job> retired;
185 std::lock_guard lock(state->mutex);
186 if (
auto it = state->pending.find(
id); it != state->pending.end()) {
187 retired = std::move(it->second);
188 state->pending.erase(it);
195 std::shared_ptr<State> state_;