Async++ unknown
Async (co_await/co_return) code for C++
Loading...
Searching...
No Matches
signal.h
1#pragma once
2#include <asyncpp/ref.h>
3
4#include <atomic>
5#include <cassert>
6#include <cstddef>
7#include <mutex>
8#include <shared_mutex>
9#include <unordered_map>
10#include <utility>
11
12namespace asyncpp {
14 using mutex_type = std::mutex;
16 };
18 struct noop_mutex {
19 constexpr void lock() noexcept {}
20 constexpr void unlock() noexcept {}
21 constexpr bool try_lock() noexcept { return true; }
22 };
23 using mutex_type = noop_mutex;
25 };
26
27 template<typename, typename = signal_traits_mt>
28 class signal;
29
30 namespace detail {
31 struct signal_node_base : intrusive_refcount<signal_node_base> {
32 virtual ~signal_node_base() noexcept = default;
33 std::atomic<size_t> counter;
34 };
35 static constexpr size_t signal_removed_counter = 0;
36 } // namespace detail
37
40 template<typename, typename>
41 friend class signal;
42
43 public:
44 explicit signal_handle(ref<detail::signal_node_base> hndl = {}) : m_node(std::move(hndl)) {}
45 explicit operator bool() const noexcept { return valid(); }
46 [[nodiscard]] bool operator!() const noexcept { return !valid(); }
47 [[nodiscard]] bool valid() const noexcept {
48 return m_node && m_node->counter != detail::signal_removed_counter;
49 }
50 void disconnect() noexcept {
51 if (m_node) m_node->counter = detail::signal_removed_counter;
52 m_node.reset();
53 }
54
55 friend inline constexpr auto operator<=>(const signal_handle& lhs, const signal_handle& rhs) noexcept {
56 return lhs.m_node.get() <=> rhs.m_node.get();
57 }
58 friend inline constexpr auto operator==(const signal_handle& lhs, const signal_handle& rhs) noexcept {
59 return lhs.m_node.get() == rhs.m_node.get();
60 }
61 friend inline constexpr auto operator!=(const signal_handle& lhs, const signal_handle& rhs) noexcept {
62 return lhs.m_node.get() != rhs.m_node.get();
63 }
64 };
65
67 signal_handle m_handle;
68
69 public:
70 //NOLINTNEXTLINE(google-explicit-constructor)
71 scoped_signal_handle(ref<detail::signal_node_base> hdl = {}) : m_handle(std::move(hdl)) {}
72 //NOLINTNEXTLINE(google-explicit-constructor)
73 scoped_signal_handle(signal_handle hdl) : m_handle(std::move(hdl)) {}
74 ~scoped_signal_handle() noexcept { m_handle.disconnect(); }
75 explicit operator bool() const noexcept { return valid(); }
76 [[nodiscard]] bool operator!() const noexcept { return !valid(); }
77 [[nodiscard]] bool valid() const noexcept { return m_handle.valid(); }
78 void disconnect() noexcept { m_handle.disconnect(); }
79 void release() noexcept { m_handle = signal_handle{}; }
80 // NOLINTNEXTLINE(google-explicit-constructor)
81 [[nodiscard]] constexpr operator signal_handle&() noexcept { return m_handle; }
82 // NOLINTNEXTLINE(google-explicit-constructor)
83 [[nodiscard]] constexpr operator const signal_handle&() const noexcept { return m_handle; }
84
85 friend inline constexpr auto operator<=>(const scoped_signal_handle& lhs,
86 const scoped_signal_handle& rhs) noexcept {
87 return lhs.m_handle <=> rhs.m_handle;
88 }
89 friend inline constexpr auto operator==(const scoped_signal_handle& lhs,
90 const scoped_signal_handle& rhs) noexcept {
91 return lhs.m_handle == rhs.m_handle;
92 }
93 friend inline constexpr auto operator!=(const scoped_signal_handle& lhs,
94 const scoped_signal_handle& rhs) noexcept {
95 return lhs.m_handle != rhs.m_handle;
96 }
97 };
98
99 template<typename... TParams, typename TTraits>
100 class signal<void(TParams...), TTraits> {
101 struct node : detail::signal_node_base {
102 ~node() noexcept override = default;
103 virtual void invoke(const TParams&...) = 0;
104
105 ref<node> next{};
106 node* previous{};
107 };
108 template<typename FN>
109 struct node_impl final : node {
110 ~node_impl() noexcept = default;
111 void invoke(const TParams&... params) override { m_fn(params...); }
112 [[no_unique_address]] FN m_fn;
113 explicit node_impl(FN&& fncb) : m_fn(std::move(fncb)) {}
114 };
115
116 public:
117 using traits_type = TTraits;
118 using handle = signal_handle;
119
120 signal() = default;
121 ~signal();
122 signal(const signal&) = delete;
123 //NOLINTNEXTLINE(performance-noexcept-move-constructor)
124 signal(signal&&);
125 signal& operator=(const signal&) = delete;
126 //NOLINTNEXTLINE(performance-noexcept-move-constructor)
127 signal& operator=(signal&&);
128
129 [[nodiscard]] size_t size() const noexcept;
130 [[nodiscard]] bool empty() const noexcept { return size() == 0; }
131
132 template<typename FN>
133 handle append(FN&& fncb);
134 template<typename FN>
135 handle prepend(FN&& fncb);
136
137 bool remove(const handle& hdl);
138 [[nodiscard]] bool owns_handle(const handle& hdl) const;
139
140 size_t operator()(const TParams&... params) const;
141
142 template<typename FN>
143 handle operator+=(FN&& fncb) {
144 return append(std::forward<decltype(fncb)>(fncb));
145 }
146
147 void operator-=(const handle& hdl) { remove(hdl); }
148
149 private:
150 mutable typename TTraits::mutex_type m_mutex{};
151 mutable ref<node> m_head{};
152 mutable node* m_tail{};
153 std::atomic<size_t> m_current_counter{1};
154
155 size_t get_next_counter() {
156 const auto result = m_current_counter.fetch_add(1, std::memory_order::seq_cst);
157 if (result == 0) { // overflow, let's reset all nodes' counters.
158 {
159 std::lock_guard lck(m_mutex);
160 auto node = m_head;
161 while (node) {
162 node->counter = 1;
163 node = node->next;
164 }
165 }
166 return m_current_counter.fetch_add(1, std::memory_order::seq_cst);
167 }
168 return result;
169 }
170
171 void free_node(ref<node>& node) const noexcept {
172 if (node->next) { node->next->previous = node->previous; }
173 if (node->previous) { node->previous->next = node->next; }
174 node->counter = detail::signal_removed_counter;
175 if (m_head == node) m_head = node->next;
176 if (m_tail == node) m_tail = node->previous;
177 }
178 };
179
180 template<typename T>
182 template<typename T>
184
185 template<typename, typename, typename = signal_traits_mt>
187
188 template<typename TEventType, typename... TParams, typename TTraits>
189 class signal_manager<TEventType, void(TParams...), TTraits> {
190 public:
191 using event_type = TEventType;
192 using signal_type = signal<void(TParams...), TTraits>;
193 using traits_type = TTraits;
194 using handle = typename signal_type::handle;
195
196 signal_manager() = default;
197 //NOLINTNEXTLINE(performance-noexcept-move-constructor)
199 //NOLINTNEXTLINE(performance-noexcept-move-constructor)
200 signal_manager& operator=(signal_manager&&);
201 signal_manager(const signal_manager&) = delete;
202 signal_manager& operator=(const signal_manager&) = delete;
203
204 template<typename FN>
205 handle append(event_type event, FN&& fncb) {
206 auto iter = find_or_create_slot(event);
207 assert(iter != m_mapping.end());
208 return iter->second.append(std::forward<decltype(fncb)>(fncb));
209 }
210 template<typename FN>
211 handle prepend(event_type event, FN&& fncb) {
212 auto iter = find_or_create_slot(event);
213 assert(iter != m_mapping.end());
214 return iter->second.prepend(std::forward<decltype(fncb)>(fncb));
215 }
216
217 bool remove(event_type event, const handle& hdl) {
218 std::shared_lock lck{m_mutex};
219 auto iter = m_mapping.find(event);
220 return iter != m_mapping.end() && iter->second.remove(hdl);
221 }
222
223 bool owns_handle(event_type event, const handle& hdl) const {
224 std::shared_lock lck{m_mutex};
225 auto iter = m_mapping.find(event);
226 return iter != m_mapping.end() && iter->second.owns_handle(hdl);
227 }
228
229 size_t invoke(event_type event, const TParams&... params) const {
230 std::shared_lock lck{m_mutex};
231 auto iter = m_mapping.find(event);
232 if (iter == m_mapping.end()) return 0;
233 return iter->second(params...);
234 }
235
236 size_t operator()(event_type event, const TParams&... params) const { return invoke(event, params...); }
237
238 size_t shrink_to_fit() {
239 std::unique_lock lck{m_mutex};
240 size_t res = 0;
241 for (auto it = m_mapping.begin(); it != m_mapping.end();) {
242 if (it->second.empty()) {
243 res++;
244 it = m_mapping.erase(it);
245 } else
246 it++;
247 }
248 return res;
249 }
250
251 private:
252 mutable std::shared_mutex m_mutex{};
253 std::unordered_map<event_type, signal_type> m_mapping;
254
255 auto find_or_create_slot(event_type evt) {
256 std::shared_lock lck{m_mutex};
257 auto iter = m_mapping.find(evt);
258 if (iter == m_mapping.end()) {
259 lck.unlock();
260 std::unique_lock unique{m_mutex};
261 iter = m_mapping.find(evt);
262 if (iter != m_mapping.end()) return iter;
263 iter = m_mapping.emplace(evt, signal_type{}).first;
264 }
265 return iter;
266 }
267 };
268
269 template<typename... TParams, typename TTraits>
270 //NOLINTNEXTLINE(performance-noexcept-move-constructor)
271 inline signal<void(TParams...), TTraits>::signal(signal&& other) {
272 std::scoped_lock lck{m_mutex, other.m_mutex};
273 m_head = std::exchange(other.m_head, nullptr);
274 m_tail = std::exchange(other.m_tail, nullptr);
275 m_current_counter.store(other.m_current_counter.exchange(1));
276 }
277
278 template<typename... TParams, typename TTraits>
279 //NOLINTNEXTLINE(performance-noexcept-move-constructor)
280 inline signal<void(TParams...), TTraits>& signal<void(TParams...), TTraits>::operator=(signal&& other) {
281 std::scoped_lock lck{m_mutex, other.m_mutex};
282 auto node = m_head;
283 m_head.reset();
284 while (node) {
285 auto next = node->next;
286 node->previous = nullptr;
287 node->next.reset();
288 node = next;
289 }
290 assert(!node);
291 m_head = std::exchange(other.m_head, nullptr);
292 m_tail = std::exchange(other.m_tail, nullptr);
293 m_current_counter.store(other.m_current_counter.exchange(1));
294 }
295
296 template<typename... TParams, typename TTraits>
297 inline signal<void(TParams...), TTraits>::~signal() {
298 auto node = m_head;
299 m_head.reset();
300 while (node) {
301 auto next = node->next;
302 node->previous = nullptr;
303 node->next.reset();
304 node = next;
305 }
306 assert(!node);
307 }
308
309 template<typename... TParams, typename TTraits>
310 inline size_t signal<void(TParams...), TTraits>::size() const noexcept {
311 std::lock_guard lck{m_mutex};
312 auto node = m_head;
313 size_t res = 0;
314 while (node) {
315 if (node->counter != detail::signal_removed_counter) res++;
316 node = node->next;
317 }
318 return res;
319 }
320
321 template<typename... TParams, typename TTraits>
322 template<typename FN>
323 inline typename signal<void(TParams...), TTraits>::handle signal<void(TParams...), TTraits>::append(FN&& fncb) {
324 ref<node> new_node(new node_impl<FN>(std::forward<decltype(fncb)>(fncb)));
325 new_node->counter = get_next_counter();
326 if (std::lock_guard lck{m_mutex}; m_head) {
327 new_node->previous = m_tail;
328 m_tail->next = new_node;
329 m_tail = new_node.get();
330 } else {
331 m_head = new_node;
332 m_tail = new_node.get();
333 }
334 return handle(static_ref_cast<detail::signal_node_base>(new_node));
335 }
336
337 template<typename... TParams, typename TTraits>
338 template<typename FN>
339 inline typename signal<void(TParams...), TTraits>::handle signal<void(TParams...), TTraits>::prepend(FN&& fncb) {
340 ref<node> new_node(new node_impl<FN>(std::forward<decltype(fncb)>(fncb)));
341 new_node->counter = get_next_counter();
342 if (std::lock_guard lck{m_mutex}; m_head) {
343 new_node->next = m_head;
344 m_head->previous = new_node.get();
345 m_head = new_node;
346 } else {
347 m_head = new_node;
348 m_tail = new_node;
349 }
350 return handle(static_ref_cast<detail::signal_node_base>(new_node));
351 }
352
353 template<typename... TParams, typename TTraits>
354 inline bool signal<void(TParams...), TTraits>::remove(const handle& hdl) {
355 auto node = static_ref_cast<signal::node>(hdl.m_node);
356 if (!node) return false;
357 std::lock_guard lck{m_mutex};
358 free_node(node);
359 return true;
360 }
361
362 template<typename... TParams, typename TTraits>
363 inline bool signal<void(TParams...), TTraits>::owns_handle(const handle& hdl) const {
364 auto node = static_ref_cast<signal::node>(hdl.m_node);
365 if (!node || node->counter == detail::signal_removed_counter) return false;
366 std::lock_guard lck{m_mutex};
367 auto handle = m_head;
368 while (handle && handle != node) {
369 handle = handle->next;
370 }
371 return node == handle;
372 }
373
374 template<typename... TParams, typename TTraits>
375 inline size_t signal<void(TParams...), TTraits>::operator()(const TParams&... params) const {
376 ref<node> node{};
377
378 {
379 std::lock_guard lck(m_mutex);
380 node = m_head;
381 }
382
383 size_t ninvoked = 0;
384 if (!node) return ninvoked;
385
386 const auto counter = m_current_counter.load(std::memory_order_acquire);
387 do {
388 if (node->counter != detail::signal_removed_counter && counter >= node->counter) {
389 node->invoke(params...);
390 ++ninvoked;
391 }
392
393 std::lock_guard lck(m_mutex);
394 if (node->counter == detail::signal_removed_counter) { free_node(node); }
395 node = node->next;
396 } while (node);
397 return ninvoked;
398 }
399} // namespace asyncpp
Intrusive refcounting base class.
Definition ref.h:63
Reference count handle.
Definition ref.h:126
constexpr T * get() const noexcept
Get the contained value.
Definition ref.h:208
void reset(T *ptr) noexcept(noexcept(refcounted_add_ref(std::declval< T * >())) &&noexcept(refcounted_remove_ref(std::declval< T * >())))
Reset the handle with a new value.
Definition ref.h:181
Definition signal.h:66
Definition signal.h:38
Definition signal.h:186
Definition signal.h:28
Definition signal.h:13
Definition signal.h:17
Threadsafe refcount policy.
Definition ref.h:27
Thread unsafe refcount policy.
Definition ref.h:40