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