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