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