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
154 : * call_member_pointer() 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_pointer(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 Function used when the "callbacks" are objects.
179 : *
180 : * In this case, we are managing a container of shared pointers to
181 : * objects. The call() function expects the first parameter to be
182 : * a member function \em pointer (it's really an offset), instead of
183 : * a parameter to the callback.
184 : *
185 : * The callback does not need to include any arguments.
186 : *
187 : * If you have more than one callback registered, they all get called
188 : * as long as the previous callback returned `true`. If a callback
189 : * returns `false`, then the loop ends early and the call_member()
190 : * function returns `false`.
191 : *
192 : * \tparam F The type of the member function to call.
193 : * \tparam ARGS The types of the list of arguments.
194 : * \param[in] func The member function that gets called.
195 : * \param[in] args The arguments to pass to the member function.
196 : *
197 : * \return true if all the callbacks returned true, false otherwise.
198 : */
199 : template<typename F, typename ... ARGS>
200 : bool call_member(F func, ARGS ... args)
201 : {
202 : auto callbacks(f_callbacks);
203 : for(auto & c : callbacks)
204 : {
205 : if(!(c.f_callback.*func)(args...))
206 : {
207 : return false;
208 : }
209 : }
210 : return true;
211 : }
212 :
213 :
214 : /** \brief Call the direct functions.
215 : *
216 : * This function is used to call all the functions found in the container.
217 : * The function is a loop which goes through all the registered functions
218 : * and call them with the specified \p args.
219 : *
220 : * If one of the functions returns `false`, then the loop stops
221 : * immediately and this function returns `false`. If all the
222 : * callback functions returns `true`, then all get called and
223 : * call_function() returns `true`.
224 : *
225 : * \tparam ARGS The types of the arguments to pass to the callbacks.
226 : * \param[in] args The arguments to pass to the callbacks.
227 : *
228 : * \return true if all the callbacks return true.
229 : */
230 : template<typename ... ARGS>
231 4 : bool call_function(ARGS ... args)
232 : {
233 8 : auto callbacks(f_callbacks);
234 7 : for(auto & c : callbacks)
235 : {
236 4 : if(!std::invoke(c.f_callback, args...))
237 : {
238 1 : return false;
239 : }
240 : }
241 3 : return true;
242 : }
243 :
244 :
245 : public:
246 : /** \brief Add a callback to this manager.
247 : *
248 : * This function inserts the given callback to this manager. By default,
249 : * the new callback goes at the end of the list of callbacks. To add the
250 : * item at a different location, the callback can be given a priority.
251 : * Higher numbers get added first. Callbacks with the same priority
252 : * get sorted in the order they are added.
253 : *
254 : * The number of callbacks is not limited.
255 : *
256 : * It is possible to add a callback while within a callback function.
257 : * However, the new callback will not be seen until the next time an
258 : * event occurs and the call() function gets called.
259 : *
260 : * \note
261 : * If the callback type is an object share pointer, then you will be
262 : * able to call any member function of that object with the call()
263 : * function. On the other hand, for direct functions, only that one
264 : * specific function is called. Direct functions can use an std::bind()
265 : * in order to attach the function to an object at runtime.
266 : *
267 : * \note
268 : * It is possible to add the same callback any number of time. It
269 : * will get called a number of time equal to the number of times
270 : * you added it to the callback manager. Duplicity is not easy to
271 : * test for objects such as std::function and std::bind.
272 : *
273 : * \note
274 : * The identifier is allowed to wrap around, although you need to
275 : * call the function 2^32 times before it happens. Right now, we
276 : * assume this never happens so the function doesn't check whether
277 : * two callbacks get the same identifier.
278 : *
279 : * \param[in] callback The callback to be added to the manager.
280 : * \param[in] priority The priority to sort the callback by.
281 : *
282 : * \return The callback identifier you can use to remove the callback.
283 : *
284 : * \sa remove_callback()
285 : * \sa call()
286 : */
287 6 : callback_id_t add_callback(value_type callback, priority_t priority = DEFAULT_PRIORITY)
288 : {
289 6 : ++f_next_id;
290 6 : if(f_next_id == NULL_CALLBACK_ID)
291 : {
292 : ++f_next_id; // LCOV_EXCL_LINE
293 : }
294 :
295 6 : auto it(std::find_if(
296 : f_callbacks.begin()
297 : , f_callbacks.end()
298 3 : , [priority](auto const & c)
299 3 : {
300 : // assuming f_next_id doesn't wrap, this is sufficient
301 : //
302 3 : return c.f_priority < priority;
303 3 : }));
304 :
305 6 : f_callbacks.emplace(
306 : it
307 : , f_next_id
308 : , callback
309 : , priority
310 : );
311 :
312 6 : return f_next_id;
313 : }
314 :
315 :
316 : /** \brief Remove a callback.
317 : *
318 : * This function removes a callback that was previously added by the
319 : * add_callback() function.
320 : *
321 : * If is possible to call this function from within a callback function
322 : * that just got called (i.e. a function can remove itself). Note that
323 : * the removal is not seen by the call() function until it gets called
324 : * again. In other words, if you remove a function that wasn't yet
325 : * called, it will still get called this time around.
326 : *
327 : * The parameter used to remove a callback is the callback identifier
328 : * returned by the add_callback() function.
329 : *
330 : * \note
331 : * Calling the function more than once with the same identifier is
332 : * allowed, although after the first call, nothing happens.
333 : *
334 : * \param[in] callback_id The identifier of the callback to be removed.
335 : *
336 : * \return true if a callback was removed, false otherwise.
337 : *
338 : * \sa add_callback()
339 : * \sa call()
340 : */
341 5 : bool remove_callback(callback_id_t callback_id)
342 : {
343 5 : auto it(std::find_if(
344 : f_callbacks.begin()
345 : , f_callbacks.end()
346 4 : , [callback_id](auto const & c)
347 4 : {
348 4 : return c.f_id == callback_id;
349 4 : }));
350 5 : if(it == f_callbacks.end())
351 : {
352 3 : return false;
353 : }
354 :
355 2 : f_callbacks.erase(it);
356 :
357 2 : return true;
358 : }
359 :
360 :
361 : /** \brief Clear the list of callbacks.
362 : *
363 : * This function clears the container holding callbacks. This is an
364 : * equivalent to the remove_callback() called once for each callback
365 : * that was added with the add_callback() function.
366 : *
367 : * \return true if the list wasn't empty on entry.
368 : */
369 2 : bool clear()
370 : {
371 2 : if(f_callbacks.empty())
372 : {
373 1 : return false;
374 : }
375 :
376 1 : f_callbacks.clear();
377 1 : return true;
378 : }
379 :
380 :
381 : /** \brief Get the size of the list of callbacks.
382 : *
383 : * This function returns the number of callbacks currently managed.
384 : * On creating of the callback_manager, the size is 0. The size
385 : * increases by one each time you call the add_callback() and it
386 : * returns true. The size decreases by one each time you call the
387 : * remove_callback() and it returns true.
388 : *
389 : * You can reset the size to zero by calling the clear() function.
390 : *
391 : * \return The current size of the list of callbacks.
392 : */
393 6 : size_t size() const
394 : {
395 6 : return f_callbacks.size();
396 : }
397 :
398 :
399 : /** \brief Check whether the list of callbacks is empty or not.
400 : *
401 : * This function returns true if the add_callback() was never called
402 : * or all the callbacks were removed either by remove_callback()
403 : * or using the clear() function.
404 : *
405 : * \return true if the list of callbacks is empty.
406 : */
407 9 : bool empty() const
408 : {
409 9 : return f_callbacks.empty();
410 : }
411 :
412 :
413 : /** \brief Call the managed callbacks.
414 : *
415 : * This function calls all the managed callbacks with the specified
416 : * \p args.
417 : *
418 : * This version of the function calls the specified member function
419 : * (the very first argument) of the objects this callback_manager
420 : * handles.
421 : *
422 : * \code
423 : * call(&foo::member_function, arg1, arg2, arg3, ...);
424 : * \endcode
425 : *
426 : * \note
427 : * This function verifies that the first parameter is a member function.
428 : * If not, then no matching call() function is found and the compiler
429 : * fails.
430 : *
431 : * \tparam F The type of member functon.
432 : * \tparam ARGS The type of each of the function arguments.
433 : * \tparam U A copy of the callback type.
434 : * \param[in] func The member function to be called.
435 : * \param[in] args The arguments to pass to the callback functions.
436 : *
437 : * \return true if all the callback functions returned true.
438 : */
439 : template<typename F, typename ... ARGS, typename U = T>
440 : typename std::enable_if<std::is_same<U, T>::value
441 : && std::is_member_function_pointer<F>::value
442 : && is_shared_ptr<U>::value
443 : , bool>::type
444 4 : call(F func, ARGS ... args)
445 : {
446 4 : return call_member_pointer(func, args...);
447 : }
448 :
449 :
450 : /** \brief Call the managed callbacks.
451 : *
452 : * This function calls all the managed callbacks with the specified
453 : * \p args.
454 : *
455 : * This version of the function calls the specified member function
456 : * (the very first argument) of the objects this callback_manager
457 : * handles.
458 : *
459 : * \code
460 : * call(&foo::member_function, arg1, arg2, arg3, ...);
461 : * \endcode
462 : *
463 : * \note
464 : * This function verifies that the first parameter is a member function.
465 : * If not, then no matching call() function is found and the compiler
466 : * fails.
467 : *
468 : * \tparam F The type of member functon.
469 : * \tparam ARGS The type of each of the function arguments.
470 : * \tparam U A copy of the callback type.
471 : * \param[in] func The member function to be called.
472 : * \param[in] args The arguments to pass to the callback functions.
473 : *
474 : * \return true if all the callback functions returned true.
475 : */
476 : template<typename F, typename ... ARGS, typename U = T>
477 : typename std::enable_if<std::is_same<U, T>::value
478 : && std::is_member_function_pointer<F>::value
479 : && !is_shared_ptr<U>::value
480 : , bool>::type
481 : call(F func, ARGS ... args)
482 : {
483 : return call_member(func, args...);
484 : }
485 :
486 :
487 : /** \brief Call the managed callbacks.
488 : *
489 : * This function calls all the managed callbacks with the specified
490 : * arguments.
491 : *
492 : * This version of the function calls a direct function which accepts
493 : * one or more arguments.
494 : *
495 : * We distinguish the first parameter in order to be able to test it
496 : * as a member function or not.
497 : *
498 : * \tparam ARGS The type of each of the function arguments.
499 : * \tparam U A copy of the callback type.
500 : * \param[in] args The arguments to pass to the callback functions.
501 : *
502 : * \return true if all the callback functions returned true.
503 : */
504 : template<typename ... ARGS, typename U = T>
505 : typename std::enable_if<std::is_same<U, T>::value
506 : && !is_shared_ptr<U>::value, bool>::type
507 4 : call(ARGS ... args)
508 : {
509 4 : return call_function(args...);
510 : }
511 :
512 :
513 : private:
514 : /** \brief The list of callbacks.
515 : *
516 : * This variable holds the list of callbacks added by the add_callback()
517 : * function. By default it is empty, meaning that the call() function
518 : * does nothing. The list can be shrunk using the remove_callback()
519 : * or the clear() functions.
520 : */
521 : callbacks_t f_callbacks = callbacks_t();
522 :
523 :
524 : /** \brief The next idenfitier.
525 : *
526 : * Each time you call the add_callback() function, this identifier gets
527 : * incremented by one. You can record that number to later remove that
528 : * specific callback from the list. If you never need to remove the
529 : * callback or you can call the clear() function, then there is no need
530 : * to save the callback identifier on return.
531 : */
532 : callback_id_t f_next_id = NULL_CALLBACK_ID;
533 : };
534 :
535 :
536 :
537 : } // snap namespacedev
538 : // vim: ts=4 sw=4 et
|