Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* @file handle.hpp
* @copyright Copyright (c) 2020 University of Turku, MIT License
* @author Nicolas Pope
*/
#pragma once
#include <ftl/threads.hpp>
#include <ftl/exception.hpp>
#include <functional>
#include <unordered_map>
namespace ftl {
struct Handle;
struct BaseHandler {
virtual void remove(const Handle &)=0;
virtual void removeUnsafe(const Handle &)=0;
inline Handle make_handle(BaseHandler*, int);
protected:
std::mutex mutex_;
int id_=0;
};
/**
* A `Handle` is used to manage registered callbacks, allowing them to be
* removed safely whenever the `Handle` instance is destroyed.
*/
struct [[nodiscard]] Handle {
friend struct BaseHandler;
/**
* Cancel the callback and invalidate the handle.
*/
inline void cancel() { if (handler_) handler_->remove(*this); handler_ = nullptr; }
inline void innerCancel() { if (handler_) handler_->removeUnsafe(*this); handler_ = nullptr; }
inline int id() const { return id_; }
Handle() : handler_(nullptr), id_(0) {}
Handle(const Handle &)=delete;
Handle &operator=(const Handle &)=delete;
inline Handle(Handle &&h) : handler_(nullptr) {
if (handler_) handler_->remove(*this);
handler_ = h.handler_;
h.handler_ = nullptr;
id_ = h.id_;
}
inline Handle &operator=(Handle &&h) {
if (handler_) handler_->remove(*this);
handler_ = h.handler_;
h.handler_ = nullptr;
id_ = h.id_;
return *this;
}
inline ~Handle() {
if (handler_) {
handler_->remove(*this);
}
}
private:
BaseHandler *handler_;
int id_;
Handle(BaseHandler *h, int id) : handler_(h), id_(id) {}
};
/**
* This class is used to manage callbacks. The template parameters are the
* arguments to be passed to the callback when triggered. This class is already
* thread-safe.
*
* POSSIBLE BUG: On destruction any remaining handles will be left with
* dangling pointer to Handler.
*/
template <typename ...ARGS>
struct Handler : BaseHandler {
Handler() {}
~Handler() {
// Ensure all thread pool jobs are done
while (jobs_ > 0 && ftl::pool.size() > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
/**
* Add a new callback function. It returns a `Handle` object that must
* remain in scope, the destructor of the `Handle` will remove the callback.
*/
Handle on(const std::function<bool(ARGS...)> &f) {
std::unique_lock<std::mutex> lk(mutex_);
int id = id_++;
callbacks_[id] = f;
return make_handle(this, id);
}
/**
* Safely trigger all callbacks. Note that `Handler` is locked when
* triggering so callbacks cannot make modifications to it or they will
* lock up. To remove a callback, return false from the callback, else
* return true.
*/
void trigger(ARGS ...args) {
std::unique_lock<std::mutex> lk(mutex_);
for (auto i=callbacks_.begin(); i!=callbacks_.end(); ) {
} catch(...) {
hadFault = true;
}
if (hadFault) throw FTL_Error("Callback exception");
}
/**
* Call all the callbacks in another thread. The callbacks are done in a
* single thread, not in parallel.
*/
void triggerAsync(ARGS ...args) {
std::unique_lock<std::mutex> lk(mutex_);
for (auto i=callbacks_.begin(); i!=callbacks_.end(); ) {
} catch (...) {
hadFault = true;
}
--jobs_;
if (hadFault) throw FTL_Error("Callback exception");
});
}
/**
* Each callback is called in its own thread job. Note: the return value
* of the callback is ignored in this case and does not allow callback
* removal via the return value.
*/
void triggerParallel(ARGS ...args) {
std::unique_lock<std::mutex> lk(mutex_);
jobs_ += callbacks_.size();
for (auto i=callbacks_.begin(); i!=callbacks_.end(); ++i) {
ftl::pool.push([this, f = i->second, args...](int id) {
try {
} catch (const ftl::exception &e) {
--jobs_;
throw e;
}
--jobs_;
});
}
}
/**
* Remove a callback using its `Handle`. This is equivalent to allowing the
* `Handle` to be destroyed or cancelled.
*/
void remove(const Handle &h) override {
{
std::unique_lock<std::mutex> lk(mutex_);
callbacks_.erase(h.id());
}
// Make sure any possible call to removed callback has finished.
while (jobs_ > 0 && ftl::pool.size() > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
void removeUnsafe(const Handle &h) override {
callbacks_.erase(h.id());
// Make sure any possible call to removed callback has finished.
while (jobs_ > 0 && ftl::pool.size() > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
void clear() {
callbacks_.clear();
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
}
private:
std::unordered_map<int, std::function<bool(ARGS...)>> callbacks_;
std::atomic_int jobs_=0;
};
/**
* This class is used to manage callbacks. The template parameters are the
* arguments to be passed to the callback when triggered. This class is already
* thread-safe. Note that this version only allows a single callback at a time
* and throws an exception if multiple are added without resetting.
*/
template <typename ...ARGS>
struct SingletonHandler : BaseHandler {
/**
* Add a new callback function. It returns a `Handle` object that must
* remain in scope, the destructor of the `Handle` will remove the callback.
*/
[[nodiscard]] Handle on(const std::function<bool(ARGS...)> &f) {
std::unique_lock<std::mutex> lk(mutex_);
if (callback_) throw FTL_Error("Callback already bound");
callback_ = f;
return make_handle(this, id_++);
}
/**
* Safely trigger all callbacks. Note that `Handler` is locked when
* triggering so callbacks cannot make modifications to it or they will
* lock up. To remove a callback, return false from the callback, else
* return true.
*/
bool trigger(ARGS ...args) {
std::unique_lock<std::mutex> lk(mutex_);
if (callback_) {
bool keep = callback_(std::forward<ARGS>(args)...);
if (!keep) callback_ = nullptr;
return keep;
} else {
return false;
}
//} catch (const std::exception &e) {
// LOG(ERROR) << "Exception in callback: " << e.what();
//}
}
/**
* Remove a callback using its `Handle`. This is equivalent to allowing the
* `Handle` to be destroyed or cancelled. If the handle does not match the
* currently bound callback then the callback is not removed.
*/
void remove(const Handle &h) override {
std::unique_lock<std::mutex> lk(mutex_);
if (h.id() == id_-1) callback_ = nullptr;
}
void removeUnsafe(const Handle &h) override {
if (h.id() == id_-1) callback_ = nullptr;
}
void reset() { callback_ = nullptr; }
operator bool() const { return (bool)callback_; }
private:
std::function<bool(ARGS...)> callback_;
};
}
ftl::Handle ftl::BaseHandler::make_handle(BaseHandler *h, int id) {
return ftl::Handle(h, id);
}