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