LCOV - code coverage report
Current view: top level - snapdev - callback_manager.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 50 50 100.0 %
Date: 2022-07-09 19:51:09 Functions: 39 42 92.9 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13