AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
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
22namespace aui::detail::signal {
23template<size_t I, typename TupleInitial, typename TupleAll>
24auto resizeTuple(TupleInitial initial, TupleAll all) {
25 if constexpr (I == 0) {
26 return initial;
27 } else {
28 return resizeTuple<I - 1>(std::tuple_cat(initial, std::make_tuple(std::get<std::tuple_size_v<TupleInitial>>(all))), all);
29 }
30}
31
32template<aui::not_overloaded_lambda Lambda, typename... Args>
33inline void callIgnoringExcessArgs(Lambda&& lambda, const Args&... args) {
34 static constexpr size_t EXPECTED_ARG_COUNT = std::tuple_size_v<typename lambda_info<std::decay_t<Lambda>>::args>;
35 auto smallerTuple = resizeTuple<EXPECTED_ARG_COUNT>(std::make_tuple(), std::make_tuple(std::cref(args)...));
36 std::apply(lambda, smallerTuple);
37}
38
39template <typename Projection>
40struct projection_info {
41 static_assert(
42 aui::pointer_to_member<Projection> || aui::not_overloaded_lambda<Projection> ||
43 aui::function_pointer<Projection>,
44 "projection is required to be an pointer-to-member or not overloaded lambda or function pointer");
45};
46
47template<aui::pointer_to_member Projection>
48struct projection_info<Projection> {
49private:
50 template <typename... T>
51 struct cat;
52
53 template <typename First, typename... T>
54 struct cat<First, std::tuple<T...>> {
55 using type = std::tuple<First, T...>;
56 };
57
58public:
59 using info = typename aui::member<Projection>;
60 using return_t = typename info::return_t;
61 using args = typename cat<typename info::clazz&, typename info::args>::type;
62};
63
64template<aui::not_overloaded_lambda Projection>
65struct projection_info<Projection> {
66 using info = typename aui::lambda_info<Projection>;
67 using return_t = typename info::return_t;
68 using args = typename info::args;
69};
70
71template<aui::function_pointer Projection>
72struct projection_info<Projection> {
73 using info = typename aui::function_info<Projection>;
74 using return_t = typename info::return_t;
75 using args = typename info::args;
76};
77
78
79template<typename AnySignal, // can't use AAnySignal here, as concept would depend on itself
80 typename Projection>
81struct ProjectedSignal {
82 friend class ::AObject;
83
84 AnySignal& base;
85 std::decay_t<Projection> projection;
86
87 ProjectedSignal(AnySignal& base, Projection projection) : base(base), projection(std::move(projection)) {}
88
89 using projection_info_t = aui::detail::signal::projection_info<Projection>;
90
91 using projection_returns_t = typename projection_info_t::return_t;
92
93 static constexpr bool IS_PROJECTION_RETURNS_TUPLE = aui::is_tuple<projection_returns_t>;
94
95 using emits_args_t = std::conditional_t<IS_PROJECTION_RETURNS_TUPLE,
96 projection_returns_t,
97 std::tuple<projection_returns_t>>;
98
99 template <convertible_to<AObjectBase*> Object, not_overloaded_lambda Lambda>
100 void connect(Object objectBase, Lambda&& lambda) {
101 base.connect(
102 objectBase,
103 tuple_visitor<typename projection_info_t::args>::for_each_all([&]<typename... ProjectionArgs>() {
104 return [invocable = std::forward<Lambda>(lambda),
105 projection = projection](const std::decay_t<ProjectionArgs>&... args) {
106 auto result = std::invoke(projection, args...);
107 if constexpr (IS_PROJECTION_RETURNS_TUPLE) {
108 std::apply(invocable, std::move(result));
109 } else {
110 std::invoke(invocable, std::move(result));
111 }
112 };
113 }));
114 }
115
116 operator bool() const {
117 return bool(base);
118 }
119
120private:
121 template <not_overloaded_lambda Lambda>
122 auto makeRawInvocable(Lambda&& lambda) const {
123 return tuple_visitor<emits_args_t>::for_each_all([&]<typename... ProjectionResult>() {
124 return [lambda = std::forward<Lambda>(lambda)](const std::decay_t<ProjectionResult>&... args){
125 aui::detail::signal::callIgnoringExcessArgs(lambda, args...);
126 };
127 });
128 }
129};
130
131}
132
133template<typename... Args>
134class ASignal final: public AAbstractSignal
135{
136 friend class AObject;
137 friend class UIDataBindingTest_APropertyPrecomputed_Complex_Test;
138 template<typename AnySignal,
139 typename Projection>
140 friend struct aui::detail::signal::ProjectedSignal;
141
142 template <typename T>
143 friend class AWatchable;
144public:
145 using func_t = std::function<void(Args...)>;
146 using emits_args_t = std::tuple<Args...>;
147
148 template<typename Projection>
149 auto projected(Projection&& projection) const {
150 return aui::detail::signal::ProjectedSignal(const_cast<ASignal&>(*this), std::forward<Projection>(projection));
151 }
152
154 ASignal& signal;
155 std::tuple<Args...> args;
156
157 void invokeSignal(AObject* emitter) {
158 signal.invokeSignal(emitter, std::move(args));
159 }
160 };
161
162 call_wrapper operator()(Args... args) {
163 return {*this, std::make_tuple(std::move(args)...)};
164 }
165
166 ASignal() = default;
167 ASignal(ASignal&&) noexcept = default;
168 ASignal(const ASignal&) = delete;
169
170 virtual ~ASignal() noexcept
171 {
172 for (const _<slot>& slot : mSlots)
173 {
174 unlinkSlot(slot->objectBase);
175 }
176 }
177
183 operator bool() const {
184 return !mSlots.empty();
185 }
186
187 void clearAllConnections() const noexcept override
188 {
189 clearAllConnectionsIf([](const auto&){ return true; });
190 }
191 void clearAllConnectionsWith(aui::no_escape<AObjectBase> object) const noexcept override
192 {
193 clearAllConnectionsIf([&](const _<slot>& p){ return p->objectBase == object.ptr(); });
194 }
195
196 [[nodiscard]]
197 bool hasConnectionsWith(aui::no_escape<AObjectBase> object) const noexcept override {
198 return std::any_of(mSlots.begin(), mSlots.end(), [&](const _<slot>& s) {
199 return s->objectBase == object.ptr();
200 });
201 }
202
203private:
204 struct slot
205 {
206 AObjectBase* objectBase;
207 AObject* object;
208 func_t func;
209 bool isDisconnected = false;
210 };
211
212 mutable AVector<_<slot>> mSlots;
213
214 void invokeSignal(AObject* emitter, const std::tuple<Args...>& args = {});
215
216 template <aui::convertible_to<AObjectBase*> Object, aui::not_overloaded_lambda Lambda>
217 void connect(Object objectBase, Lambda&& lambda) {
218 AObject* object = nullptr;
219 if constexpr (requires { object = objectBase; }) {
220 object = objectBase;
221 }
222 mSlots.push_back(_new<slot>(slot { objectBase, object, makeRawInvocable(std::forward<Lambda>(lambda)) }));
223 linkSlot(objectBase);
224 }
225
226 void addGenericObserver(AObjectBase* object, std::function<void()> observer) override {
227 connect(object, [observer = std::move(observer)] {
228 observer();
229 });
230 }
231
232 template<aui::not_overloaded_lambda Lambda>
233 auto makeRawInvocable(Lambda&& lambda) const
234 {
235 return [lambda = std::forward<Lambda>(lambda)](const Args&... args){
236 aui::detail::signal::callIgnoringExcessArgs(lambda, args...);
237 };
238 }
239
240private:
241
242 template<typename Predicate>
243 void clearAllConnectionsIf(Predicate&& predicate) const noexcept {
244 /*
245 * Removal of connections before end of execution of clearAllConnectionsIf may cause this ASignal destruction,
246 * causing undefined behaviour. Destructing these connections after mSlotsLock unlocking solves the problem.
247 */
248 AVector<func_t> slotsToRemove;
249
250 slotsToRemove.reserve(mSlots.size());
251 mSlots.removeIf([&slotsToRemove, predicate = std::move(predicate)](const _<slot>& p) {
252 if (predicate(p)) {
253 slotsToRemove << std::move(p->func);
254 return true;
255 }
256 return false;
257 });
258
259 slotsToRemove.clear();
260 }
261};
262#include <AUI/Thread/AThread.h>
263
264template <typename ... Args>
265void ASignal<Args...>::invokeSignal(AObject* emitter, const std::tuple<Args...>& args)
266{
267 if (mSlots.empty())
268 return;
269
270 _<AObject> emitterPtr, receiverPtr;
271
272 if (emitter != nullptr) {
273 if (auto sharedPtr = weakPtrFromObject(emitter).lock()) { // avoid emitter removal during signal processing
274 emitterPtr = std::move(static_cast<_<AObject>>(sharedPtr));
275 }
276 }
277
278 auto slots = std::move(mSlots); // needed to safely iterate through the slots
279 for (auto i = slots.begin(); i != slots.end();)
280 {
281 slot& slot = **i;
282 _weak<AObject> receiverWeakPtr;
283 if (slot.object != nullptr) {
284 receiverWeakPtr = weakPtrFromObject(slot.object);
285 if (slot.object->isSlotsCallsOnlyOnMyThread() && slot.object->getThread() != AThread::current()) {
286 // perform crossthread call; should make weak ptr to the object and queue call to thread message queue
287
288 /*
289 * That's because shared_ptr counting mechanism is used when doing a crossthread call.
290 * It could not track the object existence without shared_ptr block.
291 * Also, receiverWeakPtr.lock() may be null here because object is in different thread and being destructed by shared_ptr but have not reached clearSignals() yet.
292 */
293 if (receiverWeakPtr.lock() != nullptr) {
294 slot.object->getThread()->enqueue(
295 [this, receiverWeakPtr = std::move(receiverWeakPtr), slot = *i, args = args]() {
296 if (slot->isDisconnected) {
297 return;
298 }
299 if (auto receiverPtr = receiverWeakPtr.lock()) {
300 AAbstractSignal::isDisconnected() = false;
301 (std::apply)(slot->func, args);
302 if (AAbstractSignal::isDisconnected()) {
303 unlinkSlot(receiverPtr.get());
304 slot->isDisconnected = true;
305 mSlots.removeFirst(slot);
306 }
307 }
308 });
309 }
310 ++i;
311 continue;
312 }
313 }
314 AAbstractSignal::isDisconnected() = false;
315
316 if (auto sharedPtr = receiverWeakPtr.lock()) { // avoid receiver removal during signal processing
317 receiverPtr = std::move(sharedPtr);
318 }
319
320 (std::apply)(slot.func, args);
321 if (AAbstractSignal::isDisconnected()) {
322 unlinkSlot(slot.object);
323 i = slots.erase(i);
324 continue;
325 }
326 ++i;
327 }
328 AUI_MARK_AS_USED(emitterPtr);
329 AUI_MARK_AS_USED(receiverPtr);
330
331 if (mSlots.empty()) {
332 mSlots = std::move(slots);
333 } else {
334 // mSlots might be modified by a single threaded signal call. In this case merge two vectors
335 mSlots.insert(mSlots.begin(), std::make_move_iterator(slots.begin()), std::make_move_iterator(slots.end()));
336 }
337
338 AAbstractSignal::isDisconnected() = false;
339}
340
347template<typename... Args>
348using emits = ASignal<Args...>;
349
350#define signals public
351
352/*
353
354// UNCOMMENT THIS to test ProjectedSignal
355
356static_assert(requires (aui::detail::signal::ProjectedSignal<emits<int>, decltype([](int) { return double(0);})> t) {
357 requires !decltype(t)::IS_PROJECTION_RETURNS_TUPLE;
358 { decltype(t)::base } -> aui::same_as<ASignal<int>&>;
359 { decltype(t)::projection } -> aui::not_overloaded_lambda;
360 { decltype(t)::projection_returns_t{} } -> aui::same_as<double>;
361 { decltype(t)::emits_args_t{} } -> aui::same_as<std::tuple<double>>;
362 { decltype(t)::projection_info_t::args{} } -> aui::same_as<std::tuple<int>>;
363 { decltype(t)::projection_info_t::return_t{} } -> aui::same_as<double>;
364});
365
366static_assert(requires (aui::detail::signal::ProjectedSignal<emits<AString>, decltype(&AString::length)> t) {
367 requires !decltype(t)::IS_PROJECTION_RETURNS_TUPLE;
368 { decltype(t)::base } -> aui::same_as<ASignal<AString>&>;
369 { decltype(t)::emits_args_t{} } -> aui::same_as<std::tuple<size_t>>;
370 { decltype(t)::projection_returns_t{} } -> aui::same_as<size_t>;
371});
372*/
Base class for signal.
Definition AAbstractSignal.h:368
A base object class.
Definition AObject.h:39
Definition ASignal.h:135
static _< AAbstractThread > current()
Definition AThread.cpp:197
void removeIf(Predicate &&predicate) noexcept
Definition AVector.h:334
AOptional< std::size_t > removeFirst(const StoredType &item) noexcept
Definition AVector.h:130
An std::weak_ptr with AUI extensions.
Definition SharedPtrTypes.h:178
API_AUI_CORE const ACommandLineArgs & args() noexcept
Definition OSAndroid.cpp:29
ASignal< Args... > emits
A signal declaration.
Definition ASignal.h:348
#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:88
#define AUI_MARK_AS_USED(variable)
Marks the variable as being used.
Definition macros.h:50
Definition ASignal.h:153
An std::weak_ptr with AUI extensions.
Definition SharedPtrTypes.h:51