sandbox
Loading...
Searching...
No Matches
signal.hpp
1// SPDX-License-Identifier: MIT
2#ifndef LIBSBX_SIGNAL_SIGNAL_HPP_
3#define LIBSBX_SIGNAL_SIGNAL_HPP_
4
5#include <atomic>
6#include <type_traits>
7
8#include <libsbx/signals/lockable.hpp>
9#include <libsbx/signals/copy_on_write.hpp>
10#include <libsbx/signals/slot.hpp>
11#include <libsbx/signals/connection.hpp>
12#include <libsbx/signals/observer.hpp>
13
14namespace sbx::signals {
15
16template<lockable Lockable, typename... Args>
17class signal_base final : public cleanable {
18
19 template<typename L>
20 inline constexpr static auto is_thread_safe_v = !std::is_same_v<L, null_mutex>;
21
22 template<typename U, typename L>
23 using cow_type = std::conditional_t<is_thread_safe_v<L>, copy_on_write<U>, U>;
24
25 template<typename U, typename L>
26 using cow_copy_type = std::conditional_t<is_thread_safe_v<L>, copy_on_write<U>, const U&>;
27
28 using lock_type = std::unique_lock<Lockable>;
29 using slot_base = signals::slot_base<Args...>;
30 using slot_ptr = signals::slot_ptr<Args...>;
31 using slot_list_type = std::vector<slot_ptr>;
32
33 struct group_type {
34 slot_list_type slots;
35 group_id id;
36 }; // struct group_type
37
38 using list_type = std::vector<group_type>;
39
40public:
41
42 using lockable_type = Lockable;
43
44 signal_base() noexcept
45 : _is_blocked{false} {}
46
47 ~signal_base() override {
48 disconnect_all();
49 }
50
51 signal_base(const signal_base& other) = delete;
52
54 : _is_blocked{other._is_blocked.load()} {
55 auto lock = lock_type{_mutex};
56
57 using std::swap;
58 swap(_slots, other._slots);
59 }
60
61 auto operator=(const signal_base& other) -> signal_base& = delete;
62
63 auto operator=(signal_base&& other) -> signal_base& {
64 auto lock1 = lock_type{_mutex, std::defer_lock};
65 auto lock2 = lock_type{other._mutex, std::defer_lock};
66
67 std::lock(lock1, lock2);
68
69 using std::swap;
70 swap(_slots, other._slots);
71 _is_blocked.store(other._is_blocked.exchange(_is_blocked.load()));
72
73 return *this;
74 }
75
76 template<typename... As>
77 auto emit(As&&... args) -> void {
78 if (_is_blocked) {
79 return;
80 }
81
82 auto reference = _slots_reference();
83
84 for (const auto& group : cow_read(reference)) {
85 for (const auto& slot : group.slots) {
86 std::invoke(*slot, std::forward<As>(args)...);
87 }
88 }
89 }
90
91 template<typename... As>
92 auto operator()(As&& ...args) -> void {
93 emit(std::forward<As>(args)...);
94 }
95
96 template<typename... As>
97 auto operator+=(As&&... args) -> connection {
98 return connect(std::forward<As>(args)...);
99 }
100
101 template<std::invocable<Args...> Callable>
102 auto connect(Callable&& callable, group_id id = 0) -> connection {
103 using slot_type = slot<Callable, Args...>;
104
105 auto slot = _make_slot<slot_type>(std::forward<Callable>(callable), id);
106
107 auto conn = connection{slot};
108
109 _add_slot(std::move(slot));
110
111 return conn;
112 }
113
114 template<typename MemberFnPtr, typename Object>
115 requires (std::is_invocable_v<MemberFnPtr, Object, Args...> && !is_observer_v<Object> && !is_weak_ptr_compatible_v<Object>)
116 auto connect(MemberFnPtr&& member_fn_ptr, Object&& object, group_id id = 0) -> connection {
117 using slot_type = member_function_ptr_slot<MemberFnPtr, Object, Args...>;
118
119 auto slot = _make_slot<slot_type>(std::forward<MemberFnPtr>(member_fn_ptr), std::forward<Object>(object), id);
120
121 auto conn = connection{slot};
122
123 _add_slot(std::move(slot));
124
125 return conn;
126 }
127
128 template<std::invocable<Args...> Callable, typename Trackable>
129 requires (is_weak_ptr_compatible_v<Trackable>)
130 auto connect(Callable&& callable, Trackable&& trackable, group_id id = 0) -> connection {
131 using signals::to_weak;
132
133 auto weak = to_weak(std::forward<Trackable>(trackable));
134
135 using slot_type = tracked_slot<Callable, decltype(weak), Args...>;
136
137 auto slot = _make_slot<slot_type>(std::forward<Callable>(callable), weak, id);
138
139 auto conn = connection{slot};
140
141 _add_slot(std::move(slot));
142
143 return conn;
144 }
145
146 template<typename... As>
147 auto connect_scoped(As&&... args) -> scoped_connection {
148 return connect(std::forward<As>(args)...);
149 }
150
151 template<typename Callable>
152 requires (std::is_invocable_v<Callable, Args...> || (std::is_member_function_pointer_v<Callable> && function_traits<Callable>::is_disconnectable_v))
153 auto disconnect(const Callable& callable) -> std::size_t {
154 return disconnect_if ([&](const auto& slot) {
155 return slot->has_full_callable(callable);
156 });
157 }
158
159 template<typename Object>
160 requires (!std::is_invocable_v<Object, Args...> && !std::is_member_function_pointer_v<Object>)
161 auto disconnect(const Object& object) -> std::size_t {
162 return disconnect_if ([&](const auto& slot) {
163 return slot->has_object(object);
164 });
165 }
166
167 template<typename Callable, typename Object>
168 auto disconnect(const Callable& callable, const Object& object) -> std::size_t {
169 return disconnect_if ([&] (const auto& slot) {
170 return slot->has_object(object) && slot->has_callable(callable);
171 });
172 }
173
174 auto disconnect_all() -> void {
175 auto lock = lock_type{_mutex};
176
177 _clear();
178 }
179
180 void block() noexcept {
181 _is_blocked.store(true);
182 }
183
184 void unblock() noexcept {
185 _is_blocked.store(false);
186 }
187
188 bool blocked() const noexcept {
189 return _is_blocked.load();
190 }
191
192protected:
193
194 auto clean(memory::observer_ptr<slot_state> state) -> void override {
195 auto lock = lock_type{_mutex};
196
197 const auto index = state->index();
198 const auto group_id = state->group();
199
200 for (auto& group : cow_write(_slots)) {
201 if (group.id == group_id) {
202 auto& slots = group.slots;
203
204 if (index < slots.size() && slots[index] && slots[index].get() == state.get()) {
205 std::swap(slots[index], slots.back());
206 slots[index]->set_index(index);
207 slots.pop_back();
208 }
209
210 return;
211 }
212 }
213 }
214
215 template<typename Condition>
216 requires (std::is_invocable_r_v<bool, Condition, const slot_ptr&>)
217 auto disconnect_if (Condition&& condition) -> std::size_t {
218 auto lock = lock_type{_mutex};
219
220 auto& groups = cow_write(_slots);
221
222 auto count = std::size_t{0};
223
224 for (auto& group : groups) {
225 auto& slots = group.slots;
226 auto i = std::size_t{0};
227
228 while (i < slots.size()) {
229 if (std::invoke(condition, slots[i])) {
230 using std::swap;
231
232 swap(slots[i], slots.back());
233
234 slots[i]->set_index(i);
235 slots.pop_back();
236
237 ++count;
238 } else {
239 ++i;
240 }
241 }
242 }
243
244 return count;
245 }
246
247private:
248
249 auto _slots_reference() -> cow_copy_type<list_type, lockable_type> {
250 auto lock = lock_type{_mutex};
251
252 return _slots;
253 }
254
255 template<typename Slot, typename... As>
256 auto _make_slot(As&& ...args) -> std::shared_ptr<Slot> {
257 return std::make_shared<Slot>(*this, std::forward<As>(args)...);
258 }
259
260 auto _add_slot(slot_ptr&& slot) -> void {
261 const auto group_id = slot->group();
262
263 auto lock = lock_type{_mutex};
264
265 auto& groups = cow_write(_slots);
266
267
268 auto entry = groups.begin();
269
270 while (entry != groups.end() && entry->id < group_id) {
271 entry++;
272 }
273
274
275 if (entry == groups.end() || entry->id != group_id) {
276 entry = groups.insert(entry, group_type{slot_list_type{}, group_id});
277 }
278
279 // add the slot
280 slot->set_index(entry->slots.size());
281 entry->slots.push_back(std::move(slot));
282 }
283
284 auto _clear() -> void {
285 cow_write(_slots).clear();
286 }
287
288 lockable_type _mutex;
289 cow_type<list_type, Lockable> _slots;
290 std::atomic<bool> _is_blocked;
291
292}; // class signal_base
293
294template<typename... Args>
295using signal_st = signal_base<null_mutex, Args...>;
296
297template<typename... Args>
298using signal_mt = signal_base<std::mutex, Args...>;
299
300template<typename... Args>
301using signal = signal_mt<Args...>;
302
303} // namespace sbx::signals
304
305#endif // LIBSBX_SIGNAL_SIGNAL_HPP_
A non-owning pointer that can be used to observe the value of a pointer.
Definition: observer_ptr.hpp:28
Definition: connection.hpp:59
Definition: copy_on_write.hpp:11
Definition: connection.hpp:122
Definition: signal.hpp:17
Definition: slot.hpp:88
Definition: slot.hpp:146
Definition: cleanable.hpp:11
Definition: function_traits.hpp:75
Definition: lockable.hpp:16