$OpenBSD: patch-src_FbTk_Timer_cc,v 1.3 2013/02/04 09:19:44 dcoppa Exp $

Fix regression regarding timers with equal end-time
(upstream git commit 3e4ee48bf16be6925b7c35ab8bd73bd962c674d8)

Fix bug: handle the list of Timers not in-place
(upstream git commit 4d307dcd10af9d817ff5c05fc40ae7487564cb31)
See: http://sourceforge.net/tracker/?func=detail&aid=3590078&group_id=35398&atid=413960

Simplifies and fix bugs in FbTk::Timer
(upstream git commit 33161595f482d0dad950ce127b6016eefe8ea691)

--- src/FbTk/Timer.cc.orig	Mon Dec 10 18:26:54 2012
+++ src/FbTk/Timer.cc	Mon Feb  4 10:06:09 2013
@@ -52,62 +52,47 @@
 #endif
 
 #include <cstdio>
+#include <vector>
 #include <set>
 
 
 namespace {
 
 struct TimerCompare {
-    bool operator() (const FbTk::Timer* a, const FbTk::Timer* b) {
-        return a->getEndTime() < b->getEndTime();
+    // stable sort order and allows multiple timers to have
+    // the same end-time
+    bool operator() (const FbTk::Timer* a, const FbTk::Timer* b) const {
+        uint64_t ae = a->getEndTime();
+        uint64_t be = b->getEndTime();
+        return (ae < be) || (ae == be && a < b);
     }
 };
 typedef std::set<FbTk::Timer*, TimerCompare> TimerList;
 
 TimerList s_timerlist;
 
-
-/// add a timer to the static list
-void addTimer(FbTk::Timer *timer) {
-
-    assert(timer);
-    int interval = timer->getInterval();
-
-    // interval timers have their timeout change every time they are started!
-    if (interval != 0) {
-        timer->setTimeout(interval * FbTk::FbTime::IN_SECONDS);
-    }
-
-    s_timerlist.insert(timer);
 }
 
-/// remove a timer from the static list
-void removeTimer(FbTk::Timer *timer) {
 
-    assert(timer);
-    s_timerlist.erase(timer);
-}
-
-
-}
-
-
 namespace FbTk {
 
-Timer::Timer():m_timing(false), m_once(false), m_interval(0) {
+Timer::Timer() :
+    m_once(false),
+    m_interval(0),
+    m_start(0) {
 
 }
 
 Timer::Timer(const RefCount<Slot<void> > &handler):
     m_handler(handler),
-    m_timing(false),
     m_once(false),
-    m_interval(0) {
+    m_interval(0),
+    m_start(0) {
 }
 
 
 Timer::~Timer() {
-    if (isTiming()) stop();
+    stop();
 }
 
 
@@ -130,19 +115,31 @@ void Timer::setCommand(const RefCount<Slot<void> > &cm
 
 void Timer::start() {
 
-    m_start = FbTk::FbTime::now();
-
     // only add Timers that actually DO something
-    if ((! m_timing || m_interval != 0) && m_handler) {
-        m_timing = true;
-        ::addTimer(this);
+    if ( ( ! isTiming() || m_interval > 0 ) && m_handler) {
+
+        // in case start() gets triggered on a started 
+        // timer with 'm_interval != 0' we have to remove
+        // it from s_timerlist before restarting it
+        stop();
+
+        m_start = FbTk::FbTime::now();
+
+        // interval timers have their timeout change every 
+        // time they are started!
+        if (m_interval != 0) {
+            m_timeout = m_interval * FbTk::FbTime::IN_SECONDS;
+        }
+        s_timerlist.insert(this);
     }
 }
 
 
 void Timer::stop() {
-    m_timing = false;
-    ::removeTimer(this);
+    if (isTiming()) {
+        s_timerlist.erase(this);
+        m_start = 0;
+    }
 }
 
 uint64_t Timer::getEndTime() const {
@@ -195,32 +192,41 @@ void Timer::updateTimers(int fd) {
         return;
     }
 
-    now = FbTime::now();
-    for (it = s_timerlist.begin(); it != s_timerlist.end(); ) {
+    // stoping / restarting the timers modifies the list in an upredictable
+    // way. to avoid problems such as infinite loops we save the current
+    // (ordered) list of timers into a list and work on it.
 
-        // t->fireTimeout() might add timers to the list
-        // this invalidates 'it'. thus we store the current timer
-        Timer* t = *it;
-        if (now < t->getEndTime()) {
+    static std::vector<FbTk::Timer*> timeouts;
+
+    now = FbTime::now();
+    for (it = s_timerlist.begin(); it != s_timerlist.end(); ++it ) {
+        if (now < (*it)->getEndTime()) {
             break;
         }
+        timeouts.push_back(*it);
+    }
 
-        t->fireTimeout();
+    size_t i;
+    const size_t ts = timeouts.size();
+    for (i = 0; i < ts; ++i) {
 
-        // find the iterator to the timer again
-        // and continue working on the list
-        it = s_timerlist.find(t);
-        it++;
-        s_timerlist.erase(t);
+        FbTk::Timer& t = *timeouts[i];
 
-        if (! t->doOnce()) { // restart the current timer
-            t->m_timing = false;
-            t->start();
-        } else {
-            t->stop();
+        // first we stop the timer to remove it
+        // from s_timerlist
+        t.stop();
+
+        // then we call the handler which might (re)start 't'
+        // on it's own
+        t.fireTimeout();
+
+        // restart 't' if needed
+        if (!t.doOnce() && !t.isTiming()) {
+            t.start();
         }
     }
 
+    timeouts.clear();
 }
 
 
