Line data Source code
1 : // Copyright (c) 2013-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 : #pragma once
20 :
21 : // self
22 : //
23 : #include <snapdev/is_smart_pointer.h>
24 : #include <snapdev/has_member_function.h>
25 :
26 :
27 : // C++ lib
28 : //
29 : #include <algorithm>
30 : #include <functional>
31 : #include <iostream>
32 : #include <list>
33 :
34 :
35 :
36 : namespace snapdev
37 : {
38 :
39 : /** \brief Manage a set of callbacks.
40 : *
41 : * This class is capable of handling a container of objects, either direct
42 : * functions or your objects with member functions you'd like to call on
43 : * an event.
44 : *
45 : * The following shows how objects are added:
46 : *
47 : * \code
48 : * struct foo
49 : * {
50 : * typedef std::shared_ptr<foo> pointer_t;
51 : *
52 : * bool member_function(int, float, std::string const &)
53 : * {
54 : * ...
55 : * return true;
56 : * }
57 : * };
58 : * callback_manager<foo::pointer_t> callbacks;
59 : *
60 : * callbacks.add_callback(obj1);
61 : * snap::callback_manager::callback_id_t save_id(callbacks.add_callback(obj2));
62 : * callbacks.add_callback(obj3);
63 : *
64 : * // call member_function() on obj1, obj2, obj3 with parameters p1, p2, p3
65 : * //
66 : * callbacks.call(&T::member_function, p1, p2, p3);
67 : *
68 : * callbacks.remove_callback(save_id); // won't call functions on obj2 anymore
69 : * \endcode
70 : *
71 : * If you instead want to add direct function calls, you can use a function
72 : * type and directly add functions. This works with objects as well when used
73 : * with std::bind() but you can only call those functions (opposed to any one
74 : * member function in the previous example).
75 : *
76 : * \code
77 : * typedef std::function<bool (*)(int, float, std::string const &)> F;
78 : * callback_manager<F> callbacks;
79 : *
80 : * snap::callback_manager::callback_id_t save1(callbacks.add_callback(std::bind(&T::member_function, obj1, std::placeholders::_1, std::placeholders::_2, ...)));
81 : * snap::callback_manager::callback_id_t save2(callbacks.add_callback(my_func));
82 : *
83 : * // call the functions added earlier with p1, p2, p3
84 : * //
85 : * callbacks.call(p1, p2, p3);
86 : *
87 : * callbacks.remove_callback(save2);
88 : * \endcode
89 : *
90 : * Note that to be able to remove a callback, you must save the identifier
91 : * returned by the add_callback(). This works whatever the type of callback
92 : * you add (shared pointer, direct function, std::function, std::bind).
93 : *
94 : * \code
95 : * auto c = std::bind(...);
96 : * auto id = callbacks.add_callback(c);
97 : *
98 : * ...
99 : *
100 : * callbacks.remove_callback(id);
101 : * \endcode
102 : *
103 : * Of course, you may use the clear() function as well. However, that is not
104 : * always what you want.
105 : *
106 : * \tparam T The type of function or object to manage.
107 : */
108 : template<class T>
109 8 : class callback_manager
110 : {
111 : public:
112 : typedef std::shared_ptr<callback_manager<T>> pointer_t;
113 : typedef T value_type;
114 : typedef std::uint32_t callback_id_t;
115 : typedef std::int32_t priority_t;
116 :
117 : static constexpr callback_id_t const NULL_CALLBACK_ID = 0;
118 : static constexpr priority_t const DEFAULT_PRIORITY = 0;
119 :
120 : private:
121 23 : struct item_t
122 : {
123 6 : item_t(
124 : callback_id_t id
125 : , value_type callback
126 : , priority_t priority)
127 : : f_id(id)
128 : , f_callback(callback)
129 6 : , f_priority(priority)
130 : {
131 6 : }
132 :
133 : callback_id_t f_id = NULL_CALLBACK_ID;
134 : value_type f_callback = value_type();
135 : priority_t f_priority = DEFAULT_PRIORITY;
136 : };
137 :
138 :
139 : typedef std::list<item_t> callbacks_t;
140 :
141 :
142 : /** \brief Function used when the "callbacks" are objects.
143 : *
144 : * In this case, we are managing a container of shared pointers to
145 : * objects. The call() function expects the first parameter to be
146 : * a member function \em pointer (it's really an offset), instead of
147 : * a parameter to the callback.
148 : *
149 : * The callback does not need to include any arguments.
150 : *
151 : * If you have more than one callback registered, they all get called
152 : * as long as the previous callback returned `true`. If a callback
153 : * returns `false`, then the loop ends early and the call_member()
154 : * function returns `false`.
155 : *
156 : * \tparam F The type of the member function to call.
157 : * \tparam ARGS The types of the list of arguments.
158 : * \param[in] func The member function that gets called.
159 : * \param[in] args The arguments to pass to the member function.
160 : *
161 : * \return true if all the callbacks returned true, false otherwise.
162 : */
163 : template<typename F, typename ... ARGS>
164 4 : bool call_member(F func, ARGS ... args)
165 : {
166 8 : auto callbacks(f_callbacks);
167 12 : for(auto & c : callbacks)
168 : {
169 9 : if(!(c.f_callback.get()->*func)(args...))
170 : {
171 1 : return false;
172 : }
173 : }
174 3 : return true;
175 : }
176 :
177 :
178 : /** \brief Call the direct functions.
179 : *
180 : * This function is used to call all the functions found in the container.
181 : * The function is a loop which goes through all the registered functions
182 : * and call them with the specified \p args.
183 : *
184 : * If one of the functions returns `false`, then the loop stops
185 : * immediately and this function returns `false`. If all the
186 : * callback functions returns `true`, then all get called and
187 : * call_function() returns `true`.
188 : *
189 : * \tparam ARGS The types of the arguments to pass to the callbacks.
190 : * \param[in] args The arguments to pass to the callbacks.
191 : *
192 : * \return true if all the callbacks return true.
193 : */
194 : template<typename ... ARGS>
195 4 : bool call_function(ARGS ... args)
196 : {
197 8 : auto callbacks(f_callbacks);
198 7 : for(auto & c : callbacks)
199 : {
200 4 : if(!std::invoke(c.f_callback, args...))
201 : {
202 1 : return false;
203 : }
204 : }
205 3 : return true;
206 : }
207 :
208 :
209 : public:
210 : /** \brief Add a callback to this manager.
211 : *
212 : * This function inserts the given callback to this manager. By default,
213 : * the new callback goes at the end of the list of callbacks. To add the
214 : * item at a different location, the callback can be given a priority.
215 : * Higher numbers get added first. Callbacks with the same priority
216 : * get sorted in the order they are added.
217 : *
218 : * The number of callbacks is not limited.
219 : *
220 : * It is possible to add a callback while within a callback function.
221 : * However, the new callback will not be seen until the next time an
222 : * event occurs and the call() function gets called.
223 : *
224 : * \note
225 : * If the callback type is an object share pointer, then you will be
226 : * able to call any member function of that object with the call()
227 : * function. On the other hand, for direct functions, only that one
228 : * specific function is called. Direct functions can use an std::bind()
229 : * in order to attach the function to an object at runtime.
230 : *
231 : * \note
232 : * It is possible to add the same callback any number of time. It
233 : * will get called a number of time equal to the number of times
234 : * you added it to the callback manager. Duplicity is not easy to
235 : * test for objects such as std::function and std::bind.
236 : *
237 : * \note
238 : * The identifier is allowed to wrap around, although you need to
239 : * call the function 2^32 times before it happens. Right now, we
240 : * assume this never happens so the function doesn't check whether
241 : * two callbacks get the same identifier.
242 : *
243 : * \param[in] callback The callback to be added to the manager.
244 : * \param[in] priority The priority to sort the callback by.
245 : *
246 : * \return The callback identifier you can use to remove the callback.
247 : *
248 : * \sa remove_callback()
249 : * \sa call()
250 : */
251 6 : callback_id_t add_callback(T callback, priority_t priority = DEFAULT_PRIORITY)
252 : {
253 6 : ++f_next_id;
254 6 : if(f_next_id == NULL_CALLBACK_ID)
255 : {
256 : ++f_next_id; // LCOV_EXCL_LINE
257 : }
258 :
259 6 : auto it(std::find_if(
260 : f_callbacks.begin()
261 : , f_callbacks.end()
262 3 : , [priority](auto const & c)
263 3 : {
264 : // assuming f_next_id doesn't wrap, this is sufficient
265 : //
266 3 : return c.f_priority < priority;
267 3 : }));
268 :
269 6 : f_callbacks.emplace(
270 : it
271 : , f_next_id
272 : , callback
273 : , priority
274 : );
275 :
276 6 : return f_next_id;
277 : }
278 :
279 :
280 : /** \brief Remove a callback.
281 : *
282 : * This function removes a callback that was previously added by the
283 : * add_callback() function.
284 : *
285 : * If is possible to call this function from within a callback function
286 : * that just got called (i.e. a function can remove itself). Note that
287 : * the removal is not seen by the call() function until it gets called
288 : * again. In other words, if you remove a function that wasn't yet
289 : * called, it will still get called this time around.
290 : *
291 : * The parameter used to remove a callback is the callback identifier
292 : * returned by the add_callback() function.
293 : *
294 : * \note
295 : * Calling the function more than once with the same identifier is
296 : * allowed, although after the first call, nothing happens.
297 : *
298 : * \param[in] callback_id The identifier of the callback to be removed.
299 : *
300 : * \return true if a callback was removed, false otherwise.
301 : *
302 : * \sa add_callback()
303 : * \sa call()
304 : */
305 5 : bool remove_callback(callback_id_t callback_id)
306 : {
307 5 : auto it(std::find_if(
308 : f_callbacks.begin()
309 : , f_callbacks.end()
310 4 : , [callback_id](auto const & c)
311 4 : {
312 4 : return c.f_id == callback_id;
313 4 : }));
314 5 : if(it == f_callbacks.end())
315 : {
316 3 : return false;
317 : }
318 :
319 2 : f_callbacks.erase(it);
320 :
321 2 : return true;
322 : }
323 :
324 :
325 : /** \brief Clear the list of callbacks.
326 : *
327 : * This function clears the container holding callbacks. This is an
328 : * equivalent to the remove_callback() called once for each callback
329 : * that was added with the add_callback() function.
330 : *
331 : * \return true if the list wasn't empty on entry.
332 : */
333 2 : bool clear()
334 : {
335 2 : if(f_callbacks.empty())
336 : {
337 1 : return false;
338 : }
339 :
340 1 : f_callbacks.clear();
341 1 : return true;
342 : }
343 :
344 :
345 : /** \brief Get the size of the list of callbacks.
346 : *
347 : * This function returns the number of callbacks currently managed.
348 : * On creating of the callback_manager, the size is 0. The size
349 : * increases by one each time you call the add_callback() and it
350 : * returns true. The size decreases by one each time you call the
351 : * remove_callback() and it returns true.
352 : *
353 : * You can reset the size to zero by calling the clear() function.
354 : *
355 : * \return The current size of the list of callbacks.
356 : */
357 6 : size_t size() const
358 : {
359 6 : return f_callbacks.size();
360 : }
361 :
362 :
363 : /** \brief Check whether the list of callbacks is empty or not.
364 : *
365 : * This function returns true if the add_callback() was never called
366 : * or all the callbacks were removed either by remove_callback()
367 : * or using the clear() function.
368 : *
369 : * \return true if the list of callbacks is empty.
370 : */
371 9 : bool empty() const
372 : {
373 9 : return f_callbacks.empty();
374 : }
375 :
376 :
377 : /** \brief Call the managed callbacks.
378 : *
379 : * This function calls all the managed callbacks with the specified
380 : * \p args.
381 : *
382 : * This version of the function calls the specified member function
383 : * (the very first argument) of the objects this callback_manager
384 : * handles.
385 : *
386 : * \code
387 : * call(&foo::member_function, arg1, arg2, arg3, ...);
388 : * \endcode
389 : *
390 : * \note
391 : * This function verifies that the first parameter is a member function.
392 : * If not, then no matching call() function is found and the compiler
393 : * fails.
394 : *
395 : * \tparam F The type of member functon.
396 : * \tparam ARGS The type of each of the function arguments.
397 : * \tparam U A copy of the callback type.
398 : * \param[in] func The member function to be called.
399 : * \param[in] args The arguments to pass to the callback functions.
400 : *
401 : * \return true if all the callback functions returned true.
402 : */
403 : template<typename F, typename ... ARGS, typename U = T>
404 : typename std::enable_if<std::is_same<U, T>::value
405 : && std::is_member_function_pointer<F>::value
406 : && is_shared_ptr<U>::value
407 : , bool>::type
408 4 : call(F func, ARGS ... args)
409 : {
410 4 : return call_member(func, args...);
411 : }
412 :
413 :
414 : /** \brief Call the managed callbacks.
415 : *
416 : * This function calls all the managed callbacks with the specified
417 : * arguments.
418 : *
419 : * This version of the function calls a direct function which accepts
420 : * one or more arguments.
421 : *
422 : * We distinguish the first parameter in order to be able to test it
423 : * as a member function or not.
424 : *
425 : * \tparam ARGS The type of each of the function arguments.
426 : * \tparam U A copy of the callback type.
427 : * \param[in] args The arguments to pass to the callback functions.
428 : *
429 : * \return true if all the callback functions returned true.
430 : */
431 : template<typename ... ARGS, typename U = T>
432 : typename std::enable_if<std::is_same<U, T>::value
433 : && !is_shared_ptr<U>::value, bool>::type
434 4 : call(ARGS ... args)
435 : {
436 4 : return call_function(args...);
437 : }
438 :
439 :
440 : private:
441 : /** \brief The list of callbacks.
442 : *
443 : * This variable holds the list of callbacks added by the add_callback()
444 : * function. By default it is empty, meaning that the call() function
445 : * does nothing. The list can be shrunk using the remove_callback()
446 : * or the clear() functions.
447 : */
448 : callbacks_t f_callbacks = callbacks_t();
449 :
450 :
451 : /** \brief The next idenfitier.
452 : *
453 : * Each time you call the add_callback() function, this identifier gets
454 : * incremented by one. You can record that number to later remove that
455 : * specific callback from the list. If you never need to remove the
456 : * callback or you can call the clear() function, then there is no need
457 : * to save the callback identifier on return.
458 : */
459 : callback_id_t f_next_id = NULL_CALLBACK_ID;
460 : };
461 :
462 :
463 :
464 : } // snap namespacedev
465 : // vim: ts=4 sw=4 et
|