Interval scheduling wrapper for Mongoose-OS C timers











up vote
0
down vote

favorite












I've written a library for Mongoose-OS C mgos_timers. I wanted to write a wrapper to take advantage of the full potential of C++ lambdas. The original C timers allow to pass a callback function pointer.



/* Timer callback */
typedef void (*timer_callback)(void *param);

mgos_timer_id mgos_set_timer(int msecs, int flags, timer_callback cb,void *cb_arg);


This has its limitations:




The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.




Therefore I wanted to use an std::function.



This is the C++ wrapper:



Header:



#pragma once

#include <mgos_timers.h>
#include <functional>

namespace mgos_utils {
class interval {
using interval_function_t = std::function<void(void)>;
public:
interval() = default;
interval& operator=(interval&& other);
interval(int millis, interval_function_t f);
void start();
void stop();
~interval();
private:
bool running = false;
mgos_timer_id id;
int repeat_millis;
interval_function_t function;
};
}


The implementation:



#include <mgos.h>

#include <mgos_utils_interval.h>

#include <functional>
#include <memory>
#include <mgos_timers.h>

#define MGOS_TIMER_DO_ONCE false

namespace mgos_utils {

using interval_function_t = std::function<void(void)>;

interval::interval(int millis, interval_function_t f) :
repeat_millis(millis), function(f)
{
start();
}

void interval::start() {
if (!running) {
running = true;
id = mgos_set_timer(repeat_millis, MGOS_TIMER_DO_ONCE, (void* this_interval) {
auto interval = reinterpret_cast<mgos_utils::interval*>(this_interval);
if (interval->running) interval->function();
// Check again as the called function might stop the interval
if (interval->running) interval->start();
}, this);
} else {
stop();
start();
}
}

void interval::stop() {
if (running) {
running = false;
mgos_clear_timer(id);
}
}

interval& interval::operator=(interval&& other) {
other.stop();
function = other.function;
repeat_millis = other.repeat_millis;
start();
return *this;
}

interval::~interval() {
stop();
}
}


This is an example of its usage:



#include <mgos.h>

#include <mgos_utils_interval.h>
#include <memory>

class interval_test {
public:
interval_test() {
loop = mgos_utils::interval(500, [this](){
LOG(LL_INFO, ("Test interval v1 count %i", interval_count++));
if (interval_count > 5) {
loop = mgos_utils::interval(1000, [this]() {
LOG(LL_INFO, ("Test interval v2 count %i", interval_count++));
if (interval_count > 8) {
LOG(LL_INFO, ("Stop test interval"));
loop.stop();
}
});
}
});
}
private:
mgos_utils::interval loop;
int interval_count = 0;
};

std::unique_ptr<interval_test> test;

extern "C" enum mgos_app_init_result mgos_app_init(void) {

test = std::unique_ptr<interval_test>(new interval_test());

return MGOS_APP_INIT_SUCCESS;
}


I've implemented the move assignment operator to be able to initialize the timers at any point as in the example. Any ideas for a better and more efficient user interface?










share|improve this question




























    up vote
    0
    down vote

    favorite












    I've written a library for Mongoose-OS C mgos_timers. I wanted to write a wrapper to take advantage of the full potential of C++ lambdas. The original C timers allow to pass a callback function pointer.



    /* Timer callback */
    typedef void (*timer_callback)(void *param);

    mgos_timer_id mgos_set_timer(int msecs, int flags, timer_callback cb,void *cb_arg);


    This has its limitations:




    The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.




    Therefore I wanted to use an std::function.



    This is the C++ wrapper:



    Header:



    #pragma once

    #include <mgos_timers.h>
    #include <functional>

    namespace mgos_utils {
    class interval {
    using interval_function_t = std::function<void(void)>;
    public:
    interval() = default;
    interval& operator=(interval&& other);
    interval(int millis, interval_function_t f);
    void start();
    void stop();
    ~interval();
    private:
    bool running = false;
    mgos_timer_id id;
    int repeat_millis;
    interval_function_t function;
    };
    }


    The implementation:



    #include <mgos.h>

    #include <mgos_utils_interval.h>

    #include <functional>
    #include <memory>
    #include <mgos_timers.h>

    #define MGOS_TIMER_DO_ONCE false

    namespace mgos_utils {

    using interval_function_t = std::function<void(void)>;

    interval::interval(int millis, interval_function_t f) :
    repeat_millis(millis), function(f)
    {
    start();
    }

    void interval::start() {
    if (!running) {
    running = true;
    id = mgos_set_timer(repeat_millis, MGOS_TIMER_DO_ONCE, (void* this_interval) {
    auto interval = reinterpret_cast<mgos_utils::interval*>(this_interval);
    if (interval->running) interval->function();
    // Check again as the called function might stop the interval
    if (interval->running) interval->start();
    }, this);
    } else {
    stop();
    start();
    }
    }

    void interval::stop() {
    if (running) {
    running = false;
    mgos_clear_timer(id);
    }
    }

    interval& interval::operator=(interval&& other) {
    other.stop();
    function = other.function;
    repeat_millis = other.repeat_millis;
    start();
    return *this;
    }

    interval::~interval() {
    stop();
    }
    }


    This is an example of its usage:



    #include <mgos.h>

    #include <mgos_utils_interval.h>
    #include <memory>

    class interval_test {
    public:
    interval_test() {
    loop = mgos_utils::interval(500, [this](){
    LOG(LL_INFO, ("Test interval v1 count %i", interval_count++));
    if (interval_count > 5) {
    loop = mgos_utils::interval(1000, [this]() {
    LOG(LL_INFO, ("Test interval v2 count %i", interval_count++));
    if (interval_count > 8) {
    LOG(LL_INFO, ("Stop test interval"));
    loop.stop();
    }
    });
    }
    });
    }
    private:
    mgos_utils::interval loop;
    int interval_count = 0;
    };

    std::unique_ptr<interval_test> test;

    extern "C" enum mgos_app_init_result mgos_app_init(void) {

    test = std::unique_ptr<interval_test>(new interval_test());

    return MGOS_APP_INIT_SUCCESS;
    }


    I've implemented the move assignment operator to be able to initialize the timers at any point as in the example. Any ideas for a better and more efficient user interface?










    share|improve this question


























      up vote
      0
      down vote

      favorite









      up vote
      0
      down vote

      favorite











      I've written a library for Mongoose-OS C mgos_timers. I wanted to write a wrapper to take advantage of the full potential of C++ lambdas. The original C timers allow to pass a callback function pointer.



      /* Timer callback */
      typedef void (*timer_callback)(void *param);

      mgos_timer_id mgos_set_timer(int msecs, int flags, timer_callback cb,void *cb_arg);


      This has its limitations:




      The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.




      Therefore I wanted to use an std::function.



      This is the C++ wrapper:



      Header:



      #pragma once

      #include <mgos_timers.h>
      #include <functional>

      namespace mgos_utils {
      class interval {
      using interval_function_t = std::function<void(void)>;
      public:
      interval() = default;
      interval& operator=(interval&& other);
      interval(int millis, interval_function_t f);
      void start();
      void stop();
      ~interval();
      private:
      bool running = false;
      mgos_timer_id id;
      int repeat_millis;
      interval_function_t function;
      };
      }


      The implementation:



      #include <mgos.h>

      #include <mgos_utils_interval.h>

      #include <functional>
      #include <memory>
      #include <mgos_timers.h>

      #define MGOS_TIMER_DO_ONCE false

      namespace mgos_utils {

      using interval_function_t = std::function<void(void)>;

      interval::interval(int millis, interval_function_t f) :
      repeat_millis(millis), function(f)
      {
      start();
      }

      void interval::start() {
      if (!running) {
      running = true;
      id = mgos_set_timer(repeat_millis, MGOS_TIMER_DO_ONCE, (void* this_interval) {
      auto interval = reinterpret_cast<mgos_utils::interval*>(this_interval);
      if (interval->running) interval->function();
      // Check again as the called function might stop the interval
      if (interval->running) interval->start();
      }, this);
      } else {
      stop();
      start();
      }
      }

      void interval::stop() {
      if (running) {
      running = false;
      mgos_clear_timer(id);
      }
      }

      interval& interval::operator=(interval&& other) {
      other.stop();
      function = other.function;
      repeat_millis = other.repeat_millis;
      start();
      return *this;
      }

      interval::~interval() {
      stop();
      }
      }


      This is an example of its usage:



      #include <mgos.h>

      #include <mgos_utils_interval.h>
      #include <memory>

      class interval_test {
      public:
      interval_test() {
      loop = mgos_utils::interval(500, [this](){
      LOG(LL_INFO, ("Test interval v1 count %i", interval_count++));
      if (interval_count > 5) {
      loop = mgos_utils::interval(1000, [this]() {
      LOG(LL_INFO, ("Test interval v2 count %i", interval_count++));
      if (interval_count > 8) {
      LOG(LL_INFO, ("Stop test interval"));
      loop.stop();
      }
      });
      }
      });
      }
      private:
      mgos_utils::interval loop;
      int interval_count = 0;
      };

      std::unique_ptr<interval_test> test;

      extern "C" enum mgos_app_init_result mgos_app_init(void) {

      test = std::unique_ptr<interval_test>(new interval_test());

      return MGOS_APP_INIT_SUCCESS;
      }


      I've implemented the move assignment operator to be able to initialize the timers at any point as in the example. Any ideas for a better and more efficient user interface?










      share|improve this question















      I've written a library for Mongoose-OS C mgos_timers. I wanted to write a wrapper to take advantage of the full potential of C++ lambdas. The original C timers allow to pass a callback function pointer.



      /* Timer callback */
      typedef void (*timer_callback)(void *param);

      mgos_timer_id mgos_set_timer(int msecs, int flags, timer_callback cb,void *cb_arg);


      This has its limitations:




      The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.




      Therefore I wanted to use an std::function.



      This is the C++ wrapper:



      Header:



      #pragma once

      #include <mgos_timers.h>
      #include <functional>

      namespace mgos_utils {
      class interval {
      using interval_function_t = std::function<void(void)>;
      public:
      interval() = default;
      interval& operator=(interval&& other);
      interval(int millis, interval_function_t f);
      void start();
      void stop();
      ~interval();
      private:
      bool running = false;
      mgos_timer_id id;
      int repeat_millis;
      interval_function_t function;
      };
      }


      The implementation:



      #include <mgos.h>

      #include <mgos_utils_interval.h>

      #include <functional>
      #include <memory>
      #include <mgos_timers.h>

      #define MGOS_TIMER_DO_ONCE false

      namespace mgos_utils {

      using interval_function_t = std::function<void(void)>;

      interval::interval(int millis, interval_function_t f) :
      repeat_millis(millis), function(f)
      {
      start();
      }

      void interval::start() {
      if (!running) {
      running = true;
      id = mgos_set_timer(repeat_millis, MGOS_TIMER_DO_ONCE, (void* this_interval) {
      auto interval = reinterpret_cast<mgos_utils::interval*>(this_interval);
      if (interval->running) interval->function();
      // Check again as the called function might stop the interval
      if (interval->running) interval->start();
      }, this);
      } else {
      stop();
      start();
      }
      }

      void interval::stop() {
      if (running) {
      running = false;
      mgos_clear_timer(id);
      }
      }

      interval& interval::operator=(interval&& other) {
      other.stop();
      function = other.function;
      repeat_millis = other.repeat_millis;
      start();
      return *this;
      }

      interval::~interval() {
      stop();
      }
      }


      This is an example of its usage:



      #include <mgos.h>

      #include <mgos_utils_interval.h>
      #include <memory>

      class interval_test {
      public:
      interval_test() {
      loop = mgos_utils::interval(500, [this](){
      LOG(LL_INFO, ("Test interval v1 count %i", interval_count++));
      if (interval_count > 5) {
      loop = mgos_utils::interval(1000, [this]() {
      LOG(LL_INFO, ("Test interval v2 count %i", interval_count++));
      if (interval_count > 8) {
      LOG(LL_INFO, ("Stop test interval"));
      loop.stop();
      }
      });
      }
      });
      }
      private:
      mgos_utils::interval loop;
      int interval_count = 0;
      };

      std::unique_ptr<interval_test> test;

      extern "C" enum mgos_app_init_result mgos_app_init(void) {

      test = std::unique_ptr<interval_test>(new interval_test());

      return MGOS_APP_INIT_SUCCESS;
      }


      I've implemented the move assignment operator to be able to initialize the timers at any point as in the example. Any ideas for a better and more efficient user interface?







      c++ c++11






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited 2 days ago









      Jamal

      30.2k11115226




      30.2k11115226










      asked Nov 19 at 16:48









      WooWapDaBug

      362214




      362214






















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          2
          down vote



          accepted










          I'd be inclined to write that big lambda as a private static function:



          class interval {
          private:
          static void callback(void* this_interval) {
          reinterpret_cast<interval*>(this_interval)->do_it();
          }

          void do_it() {
          if (running && function) {
          function();
          }
          // Check again as the called function might stop the interval
          if (running) {
          start();
          }
          }
          };


          Note that I've added a check that function isn't empty - particularly important given that's the state of a default-constructed interval. You may prefer to just let it throw std::bad_function_call - if so, that's certainly worth a comment.





          I'd re-order the condition in start() so it doesn't need to recurse:



          void interval::start() {
          if (running) { stop(); }

          running = true;
          // etc.




          I see there's a reasonable move-assignment operator, but what about move construction? That needs to be implemented or explicitly deleted. And copy construct or assignment? If you explicitly delete or default copy/move assignment and constructor, it helps show which operations you've considered.





          Also, be aware that if function() takes some time to run, then starting the timer after it has finished will gradually drift from a standard repeating timer. That may or may not be a concern for your use, but make sure you've thought about it!






          share|improve this answer























          • Thank you for your review! The drifting effect is intended. Maybe it would be nice to warn the user if that happens, I could save the times. You are right about the copy constructors and assignments. I should delete them if I'm not planning to use them.
            – WooWapDaBug
            2 days ago











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














           

          draft saved


          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207989%2finterval-scheduling-wrapper-for-mongoose-os-c-timers%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          2
          down vote



          accepted










          I'd be inclined to write that big lambda as a private static function:



          class interval {
          private:
          static void callback(void* this_interval) {
          reinterpret_cast<interval*>(this_interval)->do_it();
          }

          void do_it() {
          if (running && function) {
          function();
          }
          // Check again as the called function might stop the interval
          if (running) {
          start();
          }
          }
          };


          Note that I've added a check that function isn't empty - particularly important given that's the state of a default-constructed interval. You may prefer to just let it throw std::bad_function_call - if so, that's certainly worth a comment.





          I'd re-order the condition in start() so it doesn't need to recurse:



          void interval::start() {
          if (running) { stop(); }

          running = true;
          // etc.




          I see there's a reasonable move-assignment operator, but what about move construction? That needs to be implemented or explicitly deleted. And copy construct or assignment? If you explicitly delete or default copy/move assignment and constructor, it helps show which operations you've considered.





          Also, be aware that if function() takes some time to run, then starting the timer after it has finished will gradually drift from a standard repeating timer. That may or may not be a concern for your use, but make sure you've thought about it!






          share|improve this answer























          • Thank you for your review! The drifting effect is intended. Maybe it would be nice to warn the user if that happens, I could save the times. You are right about the copy constructors and assignments. I should delete them if I'm not planning to use them.
            – WooWapDaBug
            2 days ago















          up vote
          2
          down vote



          accepted










          I'd be inclined to write that big lambda as a private static function:



          class interval {
          private:
          static void callback(void* this_interval) {
          reinterpret_cast<interval*>(this_interval)->do_it();
          }

          void do_it() {
          if (running && function) {
          function();
          }
          // Check again as the called function might stop the interval
          if (running) {
          start();
          }
          }
          };


          Note that I've added a check that function isn't empty - particularly important given that's the state of a default-constructed interval. You may prefer to just let it throw std::bad_function_call - if so, that's certainly worth a comment.





          I'd re-order the condition in start() so it doesn't need to recurse:



          void interval::start() {
          if (running) { stop(); }

          running = true;
          // etc.




          I see there's a reasonable move-assignment operator, but what about move construction? That needs to be implemented or explicitly deleted. And copy construct or assignment? If you explicitly delete or default copy/move assignment and constructor, it helps show which operations you've considered.





          Also, be aware that if function() takes some time to run, then starting the timer after it has finished will gradually drift from a standard repeating timer. That may or may not be a concern for your use, but make sure you've thought about it!






          share|improve this answer























          • Thank you for your review! The drifting effect is intended. Maybe it would be nice to warn the user if that happens, I could save the times. You are right about the copy constructors and assignments. I should delete them if I'm not planning to use them.
            – WooWapDaBug
            2 days ago













          up vote
          2
          down vote



          accepted







          up vote
          2
          down vote



          accepted






          I'd be inclined to write that big lambda as a private static function:



          class interval {
          private:
          static void callback(void* this_interval) {
          reinterpret_cast<interval*>(this_interval)->do_it();
          }

          void do_it() {
          if (running && function) {
          function();
          }
          // Check again as the called function might stop the interval
          if (running) {
          start();
          }
          }
          };


          Note that I've added a check that function isn't empty - particularly important given that's the state of a default-constructed interval. You may prefer to just let it throw std::bad_function_call - if so, that's certainly worth a comment.





          I'd re-order the condition in start() so it doesn't need to recurse:



          void interval::start() {
          if (running) { stop(); }

          running = true;
          // etc.




          I see there's a reasonable move-assignment operator, but what about move construction? That needs to be implemented or explicitly deleted. And copy construct or assignment? If you explicitly delete or default copy/move assignment and constructor, it helps show which operations you've considered.





          Also, be aware that if function() takes some time to run, then starting the timer after it has finished will gradually drift from a standard repeating timer. That may or may not be a concern for your use, but make sure you've thought about it!






          share|improve this answer














          I'd be inclined to write that big lambda as a private static function:



          class interval {
          private:
          static void callback(void* this_interval) {
          reinterpret_cast<interval*>(this_interval)->do_it();
          }

          void do_it() {
          if (running && function) {
          function();
          }
          // Check again as the called function might stop the interval
          if (running) {
          start();
          }
          }
          };


          Note that I've added a check that function isn't empty - particularly important given that's the state of a default-constructed interval. You may prefer to just let it throw std::bad_function_call - if so, that's certainly worth a comment.





          I'd re-order the condition in start() so it doesn't need to recurse:



          void interval::start() {
          if (running) { stop(); }

          running = true;
          // etc.




          I see there's a reasonable move-assignment operator, but what about move construction? That needs to be implemented or explicitly deleted. And copy construct or assignment? If you explicitly delete or default copy/move assignment and constructor, it helps show which operations you've considered.





          Also, be aware that if function() takes some time to run, then starting the timer after it has finished will gradually drift from a standard repeating timer. That may or may not be a concern for your use, but make sure you've thought about it!







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited 2 days ago

























          answered Nov 19 at 17:54









          Toby Speight

          22.3k537109




          22.3k537109












          • Thank you for your review! The drifting effect is intended. Maybe it would be nice to warn the user if that happens, I could save the times. You are right about the copy constructors and assignments. I should delete them if I'm not planning to use them.
            – WooWapDaBug
            2 days ago


















          • Thank you for your review! The drifting effect is intended. Maybe it would be nice to warn the user if that happens, I could save the times. You are right about the copy constructors and assignments. I should delete them if I'm not planning to use them.
            – WooWapDaBug
            2 days ago
















          Thank you for your review! The drifting effect is intended. Maybe it would be nice to warn the user if that happens, I could save the times. You are right about the copy constructors and assignments. I should delete them if I'm not planning to use them.
          – WooWapDaBug
          2 days ago




          Thank you for your review! The drifting effect is intended. Maybe it would be nice to warn the user if that happens, I could save the times. You are right about the copy constructors and assignments. I should delete them if I'm not planning to use them.
          – WooWapDaBug
          2 days ago


















           

          draft saved


          draft discarded



















































           


          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207989%2finterval-scheduling-wrapper-for-mongoose-os-c-timers%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Morgemoulin

          Scott Moir

          Souastre