From 7960363896eae55c61133bcdfd6a713942089394 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 24 Oct 2022 15:04:12 +0200 Subject: [PATCH] Make RefreshLoop constructor more generic, create add_member method. (#670) - Allows using different containers in the refreshloop consturctor argument, for example std::vector<>. - Adds an add_member(...) call to the refreshloop API to facilitate dynamically creating the member list. - Modernizes the unit tests infrastructure with fake clock such that the test does not take that much time. === Modernizes the unit tests infrastructure with fake clock such that the test does not take that much time. --- src/openlcb/RefreshLoop.cxxtest | 32 +++++++++++++++++++-- src/openlcb/RefreshLoop.hxx | 50 +++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/openlcb/RefreshLoop.cxxtest b/src/openlcb/RefreshLoop.cxxtest index abbf6f000..3e910036a 100644 --- a/src/openlcb/RefreshLoop.cxxtest +++ b/src/openlcb/RefreshLoop.cxxtest @@ -1,6 +1,8 @@ -#include "utils/async_if_test_helper.hxx" #include "openlcb/RefreshLoop.hxx" +#include "utils/async_if_test_helper.hxx" +#include "os/FakeClock.hxx" + namespace openlcb { namespace @@ -29,6 +31,7 @@ protected: MockPolling mp1_; MockPolling mp2_; std::unique_ptr loop_; + FakeClock clk_; }; TEST_F(RefreshLoopTest, Calls) @@ -36,7 +39,8 @@ TEST_F(RefreshLoopTest, Calls) EXPECT_CALL(mp1_, poll_33hz(_, _)).Times(AtLeast(3)).WillRepeatedly( WithArg<1>(Invoke(&InvokeNotification))); loop_.reset(new RefreshLoop(node_, {&mp1_})); - usleep(150000); + clk_.advance(USEC_TO_NSEC(150000)); + wait(); } TEST_F(RefreshLoopTest, CallsBoth) @@ -46,7 +50,29 @@ TEST_F(RefreshLoopTest, CallsBoth) EXPECT_CALL(mp2_, poll_33hz(_, _)).Times(AtLeast(3)).WillRepeatedly( WithArg<1>(Invoke(&InvokeNotification))); loop_.reset(new RefreshLoop(node_, {&mp1_, &mp2_})); - usleep(120000); + clk_.advance(USEC_TO_NSEC(120000)); + wait(); +} + +TEST_F(RefreshLoopTest, VectorConsturct) +{ + EXPECT_CALL(mp1_, poll_33hz(_, _)).Times(AtLeast(3)).WillRepeatedly( + WithArg<1>(Invoke(&InvokeNotification))); + std::vector v; + v.push_back(&mp1_); + loop_.reset(new RefreshLoop(node_, v)); + clk_.advance(USEC_TO_NSEC(150000)); + wait(); +} + +TEST_F(RefreshLoopTest, Add) +{ + EXPECT_CALL(mp1_, poll_33hz(_, _)).Times(AtLeast(3)).WillRepeatedly( + WithArg<1>(Invoke(&InvokeNotification))); + loop_.reset(new RefreshLoop(node_, {})); + loop_->add_member(&mp1_); + clk_.advance(USEC_TO_NSEC(150000)); + wait(); } } // namespace diff --git a/src/openlcb/RefreshLoop.hxx b/src/openlcb/RefreshLoop.hxx index b13e171da..916f23bce 100644 --- a/src/openlcb/RefreshLoop.hxx +++ b/src/openlcb/RefreshLoop.hxx @@ -59,30 +59,51 @@ public: /// Usage: Instantiate your objects from descendants of the Polling /// class. Create a RefreshLoop object and pass in the list of Polling object /// pointers to the constructor. -class RefreshLoop : public StateFlowBase +class RefreshLoop : public StateFlowBase, private Atomic { public: - RefreshLoop(Node *node, const std::initializer_list &members) + /// Constructor + /// + /// @param node openlcb Node whose interface/executor we will be using for + /// the polling loop. + /// @param members container of Polling* objects + /// (e.g. std::initializer_list or std::vector which + /// need to be polled by this refresh loop. + template > + RefreshLoop(Node *node, const Container &members) : StateFlowBase(node->iface()) , timer_(this) , lastTimeout_(os_get_time_monotonic()) - , members_(members) + , members_(members.begin(), members.end()) { start_flow(STATE(wait_for_tick)); } - /** Stops the refresh loop. If you call this funciton, then wait for the - * executor, then it is safe to delete *this. */ + /// Stops the refresh loop. If you call this funciton, then wait for the + /// executor, then it is safe to delete *this. void stop() { set_terminated(); timer_.ensure_triggered(); } + /// Adds a new member to the polling loop. + /// + /// @param new_member the member to be polled. The object ownership is + /// retained by the caller. The object must outlive this RefreshLoop + /// object. + void add_member(Polling *new_member) + { + AtomicHolder h(this); + members_.push_back(new_member); + } + + /// State which gets called after the loop is complete. Initializes the + /// next loop and sleeps until it's time to go. Action wait_for_tick() { lastTimeout_ += MSEC_TO_NSEC(30); - nextMember_ = members_.begin(); + nextMember_ = 0; // If we have overflowed our timer, this call will happen immediately. return sleep_and_call(&timer_, lastTimeout_ - os_get_time_monotonic(), STATE(call_members)); @@ -92,13 +113,18 @@ public: { while (true) { - if (nextMember_ == members_.end()) + Polling* member = nullptr; { - return call_immediately(STATE(wait_for_tick)); + AtomicHolder h(this); + if (nextMember_ >= members_.size()) + { + return call_immediately(STATE(wait_for_tick)); + } + member = members_[nextMember_]; + ++nextMember_; } bn_.reset(this); - (*nextMember_)->poll_33hz(&helper_, bn_.new_child()); - ++nextMember_; + member->poll_33hz(&helper_, bn_.new_child()); if (bn_.abort_if_almost_done()) { // The polling member called done notifiable inline. Short @@ -123,8 +149,8 @@ private: typedef vector members_type; /// The actual members. members_type members_; - /// Iterator for going through the members list. - members_type::iterator nextMember_; + /// Index for iterating through the members list. + unsigned nextMember_; }; } // namespace openlcb