1 /***************************************************************************
2 * Mechanized Assault and Exploration Reloaded Projectfile *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
18 ***************************************************************************/
19
20 #ifndef utility_signal_signalH
21 #define utility_signal_signalH
22
23 #include <list>
24 #include <tuple>
25 #include <utility>
26 #include <cassert>
27
28 #include "maxrconfig.h"
29 #include "utility/dependentfalse.h"
30 #include "utility/deref.h"
31 #include "utility/scopedoperation.h"
32 #include "utility/functiontraits.h"
33 #include "utility/thread/lockguard.h"
34 #include "utility/thread/dummymutex.h"
35
36 #include "utility/signal/signalconnection.h"
37 #include "utility/signal/signalresultcombinerlast.h"
38 #include "utility/signal/slot.h"
39 #include "utility/signal/signalcalliterator.h"
40
41 /**
42 * Basic signal class. This class should never be instantiated directly.
43 * It has a specialization for function types.
44 *
45 * @tparam FunctionSignatureType Should be a function signature type.
46 * @tparam MutexType Type of the mutex to be used in the signal class.
47 * Can be a dummy mutex if you do not need this signal to be thread safe
48 * and/or you can not effort the cost of e.g. creating the mutex.
49 * If a working mutex is used, it should be a recursive mutex to make sure no
50 * deadlocks arise when a slot functions tries to access the signal recursively.
51 * @tparam ResultCombinerType The result combiner to be used.
52 */
53 template<typename FunctionSignatureType, typename MutexType = cDummyMutex, typename ResultCombinerType = sSignalResultCombinerLast<typename sFunctionTraits<FunctionSignatureType>::result_type>>
54 class cSignal
55 {
56 static_assert (sDependentFalse<FunctionSignatureType>::value, "cSignal not allowed with this template arguments!");
57 };
58
59 /**
60 * Base class interface of signals used for type erasure and adds
61 * the possibility to disconnect connections without knowing their specific type.
62 */
63 class cSignalBase
64 {
65 public:
~cSignalBase()66 virtual ~cSignalBase() {}
67 virtual void disconnect (const cSignalConnection& connection) = 0;
68 };
69
70 class cSignalReference
71 {
72 public:
cSignalReference(cSignalBase & signal_)73 cSignalReference (cSignalBase& signal_) :
74 signal (signal_)
75 {}
76
getSignal()77 cSignalBase& getSignal()
78 {
79 return signal;
80 }
81
82 bool operator== (const cSignalReference& other) const
83 {
84 return &signal == &other.signal;
85 }
86 private:
87 cSignalBase& signal;
88 };
89
90 #if MAXR_NO_VARIADIC_TEMPLATES
91
92 #include "utility/signal/novariadic/signal_0.h"
93 #include "utility/signal/novariadic/signal_1.h"
94 #include "utility/signal/novariadic/signal_2.h"
95 #include "utility/signal/novariadic/signal_3.h"
96 #include "utility/signal/novariadic/signal_4.h"
97
98 #else
99
100 #include <limits>
101
102 /**
103 * Generic signal.
104 *
105 * A signal object provides the possibility to connect a specific type
106 * of function to it.
107 * Those connected functions will be remembered by the signal object.
108 *
109 * When the signal object is invoked later on, it calls all the connected
110 * functions with the arguments that have be passed to the signal invoke
111 * command.
112 *
113 * The signal object does not get the ownership of the connected functions
114 * and hence the user of the signal has to make sure that all connected
115 * functions are outliving the signal object.
116 *
117 * @tparam R The return value of the signal.
118 * @tparam ...Args The arguments of the signal function.
119 * @tparam ResultCombinerType
120 */
121 template<typename R, typename... Args, typename MutexType, typename ResultCombinerType>
122 class cSignal<R (Args...), MutexType, ResultCombinerType> : public cSignalBase
123 {
124 typedef cSlot<R (Args...)> SlotType;
125 typedef std::list<SlotType> SlotsContainerType;
126
127 public:
128 typedef typename ResultCombinerType::result_type result_type;
129
130 cSignal();
131
132 /**
133 * Connects a new function to the signal.
134 *
135 * @tparam F Type of the function to connect.
136 * Can be a function pointer on any function object (including lambdas).
137 * The function signature has to match the one of the signal.
138 * @param f The function object to add a connection to.
139 * @return A connection object.
140 * This connection object can be used to disconnect the function, even when
141 * the original function object is not available any longer.
142 * The user has to make sure the signal object outlives all the connection objects
143 * that are created by the signal.
144 */
145 template<typename F>
146 cSignalConnection connect (F&& f);
147
148 /**
149 * Disconnects a function object that has been connected.
150 *
151 * If the passed function object does not match any connected function
152 * it will just be ignored.
153 *
154 * @tparam F Type of the function. Has to match the type of the stored function.
155 * @param f The function to disconnect.
156 */
157 template<typename F>
158 void disconnect (const F& f);
159
160 /**
161 * Disconnect a function by the connection object that has been returned
162 * when the connection was established.
163 *
164 * If the connection object does not belong to this signal, or the connection has been
165 * disconnected already this function will just do nothing.
166 */
167 virtual void disconnect (const cSignalConnection& connection) MAXR_OVERRIDE_FUNCTION;
168
169 /**
170 * Invokes the signal.
171 *
172 * This will call all the connected functions.
173 *
174 * @param ...args The arguments to call the functions with.
175 */
176 template<typename... Args2>
177 result_type operator() (Args2&& ... args);
178 private:
179 cSignal (const cSignal& other) MAXR_DELETE_FUNCTION;
180 cSignal& operator= (const cSignal& other) MAXR_DELETE_FUNCTION;
181
182 SlotsContainerType slots;
183
184 // NOTE: may could be implemented as kind of "identifier pool" but it seems kind of
185 // overkill here for me since I don't think we will ever create as many
186 // connections as an integer can represent numbers.
187 unsigned long long nextIdentifer;
188
189 bool isInvoking;
190
191 std::shared_ptr<cSignalReference> thisReference;
192
193 // NOTE: is important that this one is a recursive mutex (as e.g the SDL_Mutex or std::recursive_mutex).
194 MutexType mutex;
195
196 void cleanUpConnections();
197 };
198
199 //------------------------------------------------------------------------------
200 template<typename R, typename... Args, typename MutexType, typename ResultCombinerType>
cSignal()201 cSignal<R (Args...), MutexType, ResultCombinerType>::cSignal() :
202 nextIdentifer (0),
203 isInvoking (false)
204 {
205 thisReference = std::make_shared<cSignalReference> (*this);
206 }
207
208 //------------------------------------------------------------------------------
209 template<typename R, typename... Args, typename MutexType, typename ResultCombinerType>
210 template<typename F>
connect(F && f)211 cSignalConnection cSignal<R (Args...), MutexType, ResultCombinerType>::connect (F&& f)
212 {
213 cLockGuard<MutexType> lock (mutex);
214
215 std::weak_ptr<cSignalReference> weakSignalRef (thisReference);
216 cSignalConnection connection (nextIdentifer++, weakSignalRef);
217 assert (nextIdentifer < std::numeric_limits<unsigned int>::max());
218
219 assert (!isInvoking); // FIXME: can lead to endless loop! fix this and remove the assert
220
221 auto slotFunction = typename SlotType::function_type (std::forward<F> (f));
222 slots.emplace_back (connection, std::move (slotFunction));
223
224 return connection;
225 }
226
227 //------------------------------------------------------------------------------
228 template<typename R, typename... Args, typename MutexType, typename ResultCombinerType>
229 template<typename F>
disconnect(const F & f)230 void cSignal<R (Args...), MutexType, ResultCombinerType>::disconnect (const F& f)
231 {
232 typedef typename std::conditional<std::is_function<F>::value, typename std::add_pointer<F>::type, F>::type test_type;
233
234 typedef typename std::conditional
235 <
236 std::is_pointer<test_type>::value,
237 typename std::conditional
238 <
239 std::is_function<typename std::remove_pointer<test_type>::type>::value,
240 std::true_type,
241 std::false_type
242 >::type,
243 std::false_type
244 >::type should_deref;
245
246 cLockGuard<MutexType> lock (mutex);
247
248 for (auto& slot : slots)
249 {
250 // NOTE: This may depend on the concrete implementation of std::function
251 // and therefor is not platform independent.
252 // This has to be rechecked with the C++ standard. If it is not
253 // platform independent we may choose to discard the disconnection
254 // by the original function object and just allow disconnection by
255 // the connection objects.
256 test_type* target = slot.function.template target<test_type> ();
257 if (target != nullptr)
258 {
259 auto& t1 = conditionalDeref (target, should_deref());
260 auto& t2 = conditionalDeref (&f, should_deref());
261 if (*t1 == *t2)
262 {
263 slot.disconnected = true;
264 }
265 }
266 }
267 }
268
269 //------------------------------------------------------------------------------
270 template<typename R, typename... Args, typename MutexType, typename ResultCombinerType>
disconnect(const cSignalConnection & connection)271 void cSignal<R (Args...), MutexType, ResultCombinerType>::disconnect (const cSignalConnection& connection)
272 {
273 cLockGuard<MutexType> lock (mutex);
274
275 for (auto& slot : slots)
276 {
277 if (slot.connection == connection)
278 {
279 slot.disconnected = true;
280 }
281 }
282
283 cleanUpConnections();
284 }
285
286 //------------------------------------------------------------------------------
287 template<typename R, typename... Args, typename MutexType, typename ResultCombinerType>
288 template<typename... Args2>
operator()289 typename cSignal<R (Args...), MutexType, ResultCombinerType>::result_type cSignal<R (Args...), MutexType, ResultCombinerType>::operator() (Args2&& ... args)
290 {
291 typedef std::tuple<Args2...> ArgumentsContainerType;
292 typedef sSignalCallIterator<R, ArgumentsContainerType, typename SlotsContainerType::const_iterator> CallIteratorType;
293
294 cLockGuard<MutexType> lock (mutex);
295
296 auto arguments = ArgumentsContainerType (std::forward<Args2> (args)...);
297
298 auto wasInvoking = isInvoking;
299 isInvoking = true;
300 auto resetter = makeScopedOperation ([&]() { isInvoking = wasInvoking; this->cleanUpConnections(); });
301
302 CallIteratorType begin (arguments, slots.begin(), slots.end());
303 CallIteratorType end (arguments, slots.end(), slots.end());
304
305 return ResultCombinerType() (begin, end);
306 }
307
308 //------------------------------------------------------------------------------
309 template<typename R, typename... Args, typename MutexType, typename ResultCombinerType>
cleanUpConnections()310 void cSignal<R (Args...), MutexType, ResultCombinerType>::cleanUpConnections()
311 {
312 if (isInvoking) return; // it is not safe to clean up yet
313
314 for (auto i = slots.begin(); i != slots.end();)
315 {
316 if (i->disconnected)
317 {
318 i = slots.erase (i);
319 }
320 else
321 {
322 ++i;
323 }
324 }
325 }
326
327 #endif // MAXR_NO_VARIADIC_TEMPLATES
328
329 #endif // utility_signal_signalH
330