-// -*- mode: C++; indent-tabs-mode: nil; -*-
+// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
#ifdef HAVE_CONFIG_H
# include "../config.h"
#include "timer.hh"
#include "display.hh"
-#include "util.hh"
-namespace otk {
-
-static timeval normalizeTimeval(const timeval &tm)
-{
- timeval ret = tm;
-
- while (ret.tv_usec < 0) {
- if (ret.tv_sec > 0) {
- --ret.tv_sec;
- ret.tv_usec += 1000000;
- } else {
- ret.tv_usec = 0;
- }
- }
-
- if (ret.tv_usec >= 1000000) {
- ret.tv_sec += ret.tv_usec / 1000000;
- ret.tv_usec %= 1000000;
- }
-
- if (ret.tv_sec < 0) ret.tv_sec = 0;
-
- return ret;
-}
-
-
-OBTimer::OBTimer(OBTimerQueueManager *m, OBTimeoutHandler h, OBTimeoutData d)
-{
- manager = m;
- handler = h;
- data = d;
-
- recur = timing = False;
-}
-
-
-OBTimer::~OBTimer(void)
-{
- if (timing) stop();
+extern "C" {
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#else
+# ifdef HAVE_UNISTD_H
+# include <sys/types.h>
+# include <unistd.h>
+# endif // HAVE_UNISTD_H
+#endif // HAVE_SYS_SELECT_H
}
+namespace otk {
-void OBTimer::setTimeout(long t)
-{
- _timeout.tv_sec = t / 1000;
- _timeout.tv_usec = t % 1000;
- _timeout.tv_usec *= 1000;
-}
-
+timeval Timer::_nearest_timeout, Timer::_now;
+Timer::TimerQ Timer::_q;
-void OBTimer::setTimeout(const timeval &t)
+void Timer::timevalAdd(timeval &a, long msec)
{
- _timeout.tv_sec = t.tv_sec;
- _timeout.tv_usec = t.tv_usec;
+ a.tv_sec += msec / 1000;
+ a.tv_usec += (msec % 1000) * 1000;
+ a.tv_sec += a.tv_usec / 1000000;
+ a.tv_usec %= 1000000;
}
-
-void OBTimer::start(void)
+bool Timer::nearestTimeout(struct timeval &tm)
{
- gettimeofday(&_start, 0);
-
- if (! timing) {
- timing = True;
- manager->addTimer(this);
+ if (_q.empty())
+ return false;
+ tm.tv_sec = _nearest_timeout.tv_sec - _now.tv_sec;
+ tm.tv_usec = _nearest_timeout.tv_usec - _now.tv_usec;
+
+ while (tm.tv_usec < 0) {
+ tm.tv_usec += 1000000;
+ tm.tv_sec--;
}
-}
-
-
-void OBTimer::stop(void)
-{
- timing = False;
-
- manager->removeTimer(this);
-}
+ tm.tv_sec += tm.tv_usec / 1000000;
+ tm.tv_usec %= 1000000;
+ if (tm.tv_sec < 0)
+ tm.tv_sec = 0;
-
-void OBTimer::halt(void)
-{
- timing = False;
+ return true;
}
-
-void OBTimer::fireTimeout(void)
+void Timer::dispatchTimers(bool wait)
{
- if (handler)
- handler(data);
-}
-
+ fd_set selset;
+ int fd;
+ timeval next;
+ Timer *curr;
+
+ gettimeofday(&_now, NULL);
+ _nearest_timeout = _now;
+ _nearest_timeout.tv_sec += 10000;
+
+ while (!_q.empty()) {
+ curr = _q.top();
+ /* since we overload the destructor to keep from removing from the middle of
+ the priority queue, set _del_me, we have to do our real delete in here.
+ */
+ if (curr->_del_me) {
+ _q.pop();
+ realDelete(curr);
+ continue;
+ }
-timeval OBTimer::timeRemaining(const timeval &tm) const
-{
- timeval ret = endpoint();
+ // the queue is sorted, so if this timer shouldn't fire, none are ready
+ _nearest_timeout = curr->_timeout;
+ if (!timercmp(&_now, &_nearest_timeout, >))
+ break;
- ret.tv_sec -= tm.tv_sec;
- ret.tv_usec -= tm.tv_usec;
+ /* we set the last fired time to delay msec after the previous firing, then
+ re-insert. timers maintain their order and may trigger more than once if
+ they've waited more than one delay's worth of time.
+ */
+ _q.pop();
+ timevalAdd(curr->_last, curr->_delay);
+ curr->_action(curr->_data);
+ timevalAdd(curr->_timeout, curr->_delay);
+ _q.push(curr);
+ }
- return normalizeTimeval(ret);
+ if (wait) {
+ // wait for the nearest trigger, or for X to do something interesting
+ fd = ConnectionNumber(**display);
+ FD_ZERO(&selset);
+ FD_SET(fd, &selset);
+ if (nearestTimeout(next))
+ select(fd + 1, &selset, NULL, NULL, &next);
+ else
+ select(fd + 1, &selset, NULL, NULL, NULL);
+ }
}
-
-timeval OBTimer::endpoint(void) const
+Timer::Timer(long delay, Timer::TimeoutHandler action, void *data)
+ : _delay(delay),
+ _action(action),
+ _data(data),
+ _del_me(false),
+ _last(_now),
+ _timeout(_now)
{
- timeval ret;
-
- ret.tv_sec = _start.tv_sec + _timeout.tv_sec;
- ret.tv_usec = _start.tv_usec + _timeout.tv_usec;
-
- return normalizeTimeval(ret);
+ timevalAdd(_timeout, delay);
+ _q.push(this);
}
-
-bool OBTimer::shouldFire(const timeval &tm) const
+void Timer::operator delete(void *self)
{
- timeval end = endpoint();
-
- return ! ((tm.tv_sec < end.tv_sec) ||
- (tm.tv_sec == end.tv_sec && tm.tv_usec < end.tv_usec));
+ Timer *t;
+ t = (Timer *)self;
+ t->_del_me = true;
}
-
-void OBTimerQueueManager::fire()
+void Timer::realDelete(Timer *me)
{
- fd_set rfds;
- timeval now, tm, *timeout = (timeval *) 0;
-
- const int xfd = ConnectionNumber(otk::OBDisplay::display);
-
- FD_ZERO(&rfds);
- FD_SET(xfd, &rfds); // break on any x events
-
- if (! timerList.empty()) {
- const OBTimer* const timer = timerList.top();
-
- gettimeofday(&now, 0);
- tm = timer->timeRemaining(now);
-
- timeout = &tm;
- }
-
- select(xfd + 1, &rfds, 0, 0, timeout);
-
- // check for timer timeout
- gettimeofday(&now, 0);
-
- // there is a small chance for deadlock here:
- // *IF* the timer list keeps getting refreshed *AND* the time between
- // timer->start() and timer->shouldFire() is within the timer's period
- // then the timer will keep firing. This should be VERY near impossible.
- while (! timerList.empty()) {
- OBTimer *timer = timerList.top();
- if (! timer->shouldFire(now))
- break;
-
- timerList.pop();
-
- timer->fireTimeout();
- timer->halt();
- if (timer->isRecurring())
- timer->start();
- }
+ ::delete me;
}
-
-void OBTimerQueueManager::addTimer(OBTimer *timer)
+void Timer::initialize(void)
{
- assert(timer);
- timerList.push(timer);
+ gettimeofday(&_now, NULL);
+ _nearest_timeout.tv_sec = 100000;
+ _nearest_timeout.tv_usec = 0;
}
-void OBTimerQueueManager::removeTimer(OBTimer* timer)
+void Timer::destroy(void)
{
- assert(timer);
- timerList.release(timer);
+ while(!_q.empty()) {
+ realDelete(_q.top());
+ _q.pop();
+ }
}
}