LCOV - code coverage report
Current view: top level - snapdev - callback_manager.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 49 49 100.0 %
Date: 2023-05-29 16:11:08 Functions: 29 32 90.6 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14