AUI Framework  master
Cross-platform module-based framework for developing C++20 desktop applications
ASignal.h
1/*
2 * AUI Framework - Declarative UI toolkit for modern C++20
3 * Copyright (C) 2020-2024 Alex2772 and Contributors
4 *
5 * SPDX-License-Identifier: MPL-2.0
6 *
7 * This Source Code Form is subject to the terms of the Mozilla Public
8 * License, v. 2.0. If a copy of the MPL was not distributed with this
9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 */
11
12#pragma once
13
14#include <algorithm>
15#include <functional>
16#include "AUI/Common/ADeque.h"
17#include "AUI/Common/AObject.h"
18#include "AUI/Thread/AMutex.h"
19#include "AAbstractSignal.h"
20#include "AUI/Traits/values.h"
21
22template<typename... Args>
23class ASignal final: public AAbstractSignal
24{
25 friend class AObject;
26
27 template <typename T>
28 friend class AWatchable;
29public:
30 using func_t = std::function<void(Args...)>;
31 using args_t = std::tuple<Args...>;
32
33private:
34 struct slot
35 {
36 AObject* object; // TODO replace with weak_ptr
37 func_t func;
38 bool isDisconnected = false;
39 };
40
41 AVector<_<slot>> mSlots;
42
43 void invokeSignal(AObject* emitter, const std::tuple<Args...>& args = {});
44
45 template<typename Lambda, typename... A>
46 struct argument_ignore_helper {};
47
48 // empty arguments
49 template<typename Lambda>
50 struct argument_ignore_helper<void(Lambda::*)() const>
51 {
52 Lambda l;
53
54 explicit argument_ignore_helper(Lambda l)
55 : l(l)
56 {
57 }
58
59 void operator()(Args... args) {
60 l();
61 }
62 };
63
64 template<typename Lambda, typename A1>
65 struct argument_ignore_helper<void(Lambda::*)(A1) const>
66 {
67 Lambda l;
68
69 explicit argument_ignore_helper(Lambda l)
70 : l(l)
71 {
72 }
73
74 template<typename... Others>
75 void call(A1&& a1, Others...)
76 {
77 l(std::move(a1));
78 }
79
80 void operator()(Args&&... args) {
81 call(std::move(args)...);
82 }
83 };
84 template<typename Lambda, typename A1, typename A2>
85 struct argument_ignore_helper<void(Lambda::*)(A1, A2) const>
86 {
87 Lambda l;
88
89 explicit argument_ignore_helper(Lambda l)
90 : l(l)
91 {
92 }
93
94 template<typename... Others>
95 void call(A1&& a1, A2&& a2, Others...)
96 {
97 l(std::move(a1), std::move(a2));
98 }
99
100 void operator()(Args&&... args) {
101 call(std::move(args)...);
102 }
103 };
104
105 template<typename Lambda, typename A1, typename A2, typename A3>
106 struct argument_ignore_helper<void(Lambda::*)(A1, A2, A3) const>
107 {
108 Lambda l;
109
110 explicit argument_ignore_helper(Lambda l)
111 : l(l)
112 {
113 }
114
115 template<typename... Others>
116 void call(A1&& a1, A2&& a2, A3&& a3, Others...)
117 {
118 l(std::move(a1), std::move(a2), std::move<A3>(a3));
119 }
120
121 void operator()(Args&&... args) {
122 call(std::move(args)...);
123 }
124 };
125
126
127
128 // Member function
129 template<class Derived, class Object, typename... FArgs>
130 void connect(Derived derived, void(Object::* memberFunction)(FArgs...))
131 {
132 Object* object = static_cast<Object*>(derived);
133 connect(object, [object, memberFunction](FArgs... args)
134 {
135 (object->*memberFunction)(args...);
136 });
137 }
138
139 // Lambda function
140 template<class Object, class Lambda>
141 void connect(Object object, Lambda lambda)
142 {
143 static_assert(std::is_class_v<Lambda>, "the lambda should be a class");
144
145 mSlots.push_back(_new<slot>(slot{ object, argument_ignore_helper<decltype(&Lambda::operator())>(lambda) }));
146
147 linkSlot(object);
148 }
149
150public:
151
153 ASignal& signal;
154 std::tuple<Args...> args;
155
156 void invokeSignal(AObject* emitter) {
157 signal.invokeSignal(emitter, std::move(args));
158 }
159 };
160
161 call_wrapper operator()(Args... args) {
162 return {*this, std::make_tuple(std::move(args)...)};
163 }
164
165 ASignal() = default;
166 ASignal(ASignal&&) noexcept = default;
167 ASignal(const ASignal&) = delete;
168
169 virtual ~ASignal() noexcept
170 {
171 for (const _<slot>& slot : mSlots)
172 {
173 unlinkSlot(slot->object);
174 }
175 }
176
182 operator bool() const {
183 return !mSlots.empty();
184 }
185
186 void clearAllConnections() noexcept override
187 {
188 clearAllConnectionsIf([](const auto&){ return true; });
189 }
190 void clearAllConnectionsWith(aui::no_escape<AObject> object) noexcept override
191 {
192 clearAllConnectionsIf([&](const _<slot>& p){ return p->object == object.ptr(); });
193 }
194
195 [[nodiscard]]
196 bool hasConnectionsWith(aui::no_escape<AObject> object) noexcept {
197 return std::any_of(mSlots.begin(), mSlots.end(), [&](const _<slot>& s) {
198 return s->object == object.ptr();
199 });
200 }
201
202private:
203
204 template<typename Predicate>
205 void clearAllConnectionsIf(Predicate&& predicate) noexcept {
206 /*
207 * Removal of connections before end of execution of clearAllConnectionsIf may cause this ASignal destruction,
208 * causing undefined behaviour. Destructing these connections after mSlotsLock unlocking solves the problem.
209 */
210 AVector<func_t> slotsToRemove;
211
212 slotsToRemove.reserve(mSlots.size());
213 mSlots.removeIf([&slotsToRemove, predicate = std::move(predicate)](const _<slot>& p) {
214 if (predicate(p)) {
215 slotsToRemove << std::move(p->func);
216 return true;
217 }
218 return false;
219 });
220
221 slotsToRemove.clear();
222 }
223};
224#include <AUI/Thread/AThread.h>
225
226template <typename ... Args>
227void ASignal<Args...>::invokeSignal(AObject* emitter, const std::tuple<Args...>& args)
228{
229 if (mSlots.empty())
230 return;
231
232 _<AObject> emitterPtr, receiverPtr;
233
234 if (auto sharedPtr = weakPtrFromObject(emitter).lock()) { // avoid emitter removal during signal processing
235 emitterPtr = std::move(static_cast<_<AObject>>(sharedPtr));
236 }
237
238 auto slots = std::move(mSlots); // needed to safely iterate through the slots
239 for (auto i = slots.begin(); i != slots.end();)
240 {
241 slot& slot = **i;
242 auto receiverWeakPtr = weakPtrFromObject(slot.object);
243 if (slot.object->isSlotsCallsOnlyOnMyThread() && slot.object->getThread() != AThread::current())
244 {
245 // perform crossthread call; should make weak ptr to the object and queue call to thread message queue
246
247 /*
248 * That's because shared_ptr counting mechanism is used when doing a crossthread call.
249 * It could not track the object existence without shared_ptr block.
250 * Also, receiverWeakPtr.lock() may be null here because object is in different thread and being destructed
251 * by shared_ptr but have not reached clearSignals() yet.
252 */
253 if (receiverWeakPtr.lock() != nullptr) {
254 slot.object->getThread()->enqueue([this,
255 receiverWeakPtr = std::move(receiverWeakPtr),
256 slot = *i,
257 args = args]() {
258 if (slot->isDisconnected) {
259 return;
260 }
261 if (auto receiverPtr = receiverWeakPtr.lock()) {
262 AAbstractSignal::isDisconnected() = false;
263 (std::apply)(slot->func, args);
264 if (AAbstractSignal::isDisconnected()) {
265 unlinkSlot(receiverPtr.get());
266 slot->isDisconnected = true;
267 mSlots.removeFirst(slot);
268 }
269 }
270 });
271 }
272 ++i;
273 }
274 else
275 {
276 AAbstractSignal::isDisconnected() = false;
277
278 if (auto sharedPtr = receiverWeakPtr.lock()) { // avoid receiver removal during signal processing
279 receiverPtr = std::move(sharedPtr);
280 }
281
282 if (!receiverPtr || receiverPtr->isSignalsEnabled()) {
283 (std::apply)(slot.func, args);
284 if (AAbstractSignal::isDisconnected()) {
285 unlinkSlot(slot.object);
286 i = slots.erase(i);
287 continue;
288 }
289 }
290 ++i;
291 }
292 }
293 AUI_MARK_AS_USED(emitterPtr);
294 AUI_MARK_AS_USED(receiverPtr);
295
296 if (mSlots.empty()) {
297 mSlots = std::move(slots);
298 } else {
299 // mSlots might be modified by a single threaded signal call. In this case merge two vectors
300 mSlots.insert(mSlots.begin(), std::make_move_iterator(slots.begin()), std::make_move_iterator(slots.end()));
301 }
302
303 AAbstractSignal::isDisconnected() = false;
304}
305
313template<typename... Args>
314using emits = ASignal<Args...>;
315
316#define signals public
Base class for signal.
Definition: AAbstractSignal.h:368
A base object class.
Definition: AObject.h:49
Definition: ASignal.h:24
static _< AAbstractThread > current()
Definition: AThread.cpp:221
A std::vector with AUI extensions.
Definition: AVector.h:38
void removeIf(Predicate &&predicate) noexcept
Definition: AVector.h:333
An std::weak_ptr with AUI extensions.
Definition: SharedPtrTypes.h:177
API_AUI_CORE const ACommandLineArgs & args() noexcept
Definition: OSAndroid.cpp:29
#define slot(v)
Passes some variable and type of the variable separated by comma. It's convenient to use with the con...
Definition: kAUI.h:90
#define AUI_MARK_AS_USED(variable)
Marks the variable as being used.
Definition: macros.h:50
Definition: ASignal.h:152
Does not allow escaping, allowing to accept lvalue ref, rvalue ref, shared_ptr and etc without overhe...
Definition: values.h:127