Handle offset change in PhasedLoop and PhasedLoopHandler

When the PhasedLoop offset_ is changed, the last_time_ must be adjusted
to have an even interval. Additionally, in the PhaseLoopHandler Call
method, schedule has to be called after fn_ because if fn_ changes
the offset then it will not be updated on the next callback since
schedule has already scheduled the next time.

Change-Id: I12110134f00ee948f986e0df9f8304fbc2b08c31
Signed-off-by: milind <milind.upadhyay@gmail.com>
diff --git a/aos/util/phased_loop.cc b/aos/util/phased_loop.cc
index 1e520b1..53f6f4d 100644
--- a/aos/util/phased_loop.cc
+++ b/aos/util/phased_loop.cc
@@ -18,6 +18,9 @@
 void PhasedLoop::set_interval_and_offset(
     const monotonic_clock::duration interval,
     const monotonic_clock::duration offset) {
+  // Update last_time_ to the new offset so that we have an even interval
+  last_time_ += offset - offset_;
+
   interval_ = interval;
   offset_ = offset;
   CHECK(offset_ >= monotonic_clock::duration(0));
@@ -55,6 +58,7 @@
   next_time += offset_;
 
   const monotonic_clock::duration difference = next_time - last_time_;
+
   const int result = difference / interval_;
   CHECK_EQ(
       0, (next_time - offset_).time_since_epoch().count() % interval_.count());
@@ -64,5 +68,5 @@
   return result;
 }
 
-}  // namespace timing
+}  // namespace time
 }  // namespace aos
diff --git a/aos/util/phased_loop_test.cc b/aos/util/phased_loop_test.cc
index 1c766c4..96eeb5c 100644
--- a/aos/util/phased_loop_test.cc
+++ b/aos/util/phased_loop_test.cc
@@ -217,6 +217,39 @@
   }
 }
 
+// Tests that the phased loop is correctly adjusting when the offset is
+// decremented multiple times.
+TEST_F(PhasedLoopTest, DecrementingOffset) {
+  constexpr int kCount = 5;
+  constexpr int kIterations = 10;
+  const auto kOffset = milliseconds(400);
+  const auto kInterval = milliseconds(1000);
+  const auto kAllIterationsInterval = kInterval * kIterations;
+
+  PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
+  auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
+  ASSERT_EQ(5, loop.Iterate(last_time));
+  for (int i = 1; i < kCount; i++) {
+    const auto offset = kOffset - milliseconds(i);
+    loop.set_interval_and_offset(kInterval, offset);
+    const auto next_time = last_time - milliseconds(1) + kAllIterationsInterval;
+    EXPECT_EQ(kIterations, loop.Iterate(next_time));
+    last_time = next_time;
+  }
+}
+
+// Tests that the phased loop is correctly adjusting when the offset is
+// changed to 0.
+TEST_F(PhasedLoopTest, ChangingOffset) {
+  const auto kOffset = milliseconds(900);
+  const auto kInterval = milliseconds(1000);
+  PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
+  const auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
+  ASSERT_EQ(5, loop.Iterate(last_time));
+  loop.set_interval_and_offset(kInterval, milliseconds(0));
+  EXPECT_EQ(4, loop.Iterate((last_time - kOffset) + (kInterval * 4)));
+}
+
 }  // namespace testing
 }  // namespace time
 }  // namespace aos