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

Generated by: LCOV version 1.13