--- /dev/null
+
+/*] Copyright (c) 2009-2010, Charles McGarvey [**************************
+**] All rights reserved.
+*
+* vi:ts=4 sw=4 tw=75
+*
+* Distributable under the terms and conditions of the 2-clause BSD license;
+* see the file COPYING for a complete text of the license.
+*
+**************************************************************************/
+
+#include <cerrno>
+#include <ctime>
+#include <limits>
+
+#include <SDL/SDL.h>
+
+#include "log.hh"
+#include "timer.hh"
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+namespace moof {
+
+
+scalar timer::gNextFire = std::numeric_limits<scalar>::max();
+std::map<unsigned,timer*> timer::gTimers;
+
+
+unsigned timer::new_identifier()
+{
+ static unsigned id = 1;
+ return id++;
+}
+
+
+void timer::init(const function& function, scalar seconds, mode mode)
+{
+ invalidate();
+
+ mode_ = mode;
+
+ if (mode_ != invalid)
+ {
+ function_ = function;
+
+ if (mode == absolute)
+ {
+ absolute_ = seconds;
+ }
+ else
+ {
+ absolute_ = seconds - ticks();
+ interval_ = seconds;
+ }
+
+ id_ = new_identifier();
+ gTimers.insert(std::pair<unsigned,timer*>(id_, this));
+
+ if (absolute_ < gNextFire) gNextFire = absolute_;
+ }
+}
+
+
+bool timer::is_valid() const
+{
+ return mode_ != invalid;
+}
+
+void timer::invalidate()
+{
+ if (mode_ != invalid)
+ {
+ gTimers.erase(id_);
+ mode_ = invalid;
+
+ if (is_equal(absolute_, gNextFire)) gNextFire = find_next_expiration();
+ }
+}
+
+
+void timer::fire()
+{
+ scalar t = ticks();
+
+ if (function_) function_(*this, t);
+
+ if (is_repeating())
+ {
+ scalar absolute = absolute_;
+
+ if (is_equal(absolute_, t, 1.0)) absolute_ += interval_;
+ else absolute_ = interval_ + t;
+
+ if (is_equal(absolute, gNextFire)) gNextFire = find_next_expiration();
+ }
+ else
+ {
+ invalidate();
+ }
+}
+
+
+scalar timer::find_next_expiration()
+{
+ std::map<unsigned,timer*>::iterator it;
+ scalar nextFire = std::numeric_limits<scalar>::max();
+
+ for (it = gTimers.begin(); it != gTimers.end(); ++it)
+ {
+ scalar absolute = (*it).second->absolute_;
+ if (absolute < nextFire) nextFire = absolute;
+ }
+
+ return nextFire;
+}
+
+
+scalar timer::seconds_remaining() const
+{
+ return absolute_ - ticks();
+}
+
+bool timer::is_expired() const
+{
+ return seconds_remaining() < 0.0;
+}
+
+bool timer::is_repeating() const
+{
+ return mode_ == repeat;
+}
+
+
+void timer::fire_expired_timers()
+{
+ fire_expired_timers(ticks());
+}
+
+void timer::fire_expired_timers(scalar t)
+{
+ std::map<unsigned,timer*>::iterator it;
+
+ if (gNextFire > t) return;
+
+ for (it = gTimers.begin(); it != gTimers.end(); ++it)
+ {
+ timer* timer = (*it).second;
+ if (timer->is_expired()) timer->fire();
+ }
+}
+
+
+#if HAVE_CLOCK_GETTIME
+
+// Since the monotonic clock will provide us with the time since the
+// computer started, the number of seconds since that time could easily
+// become so large that it cannot be accurately stored in a float (even
+// with as little two days uptime), therefore we need to start from a more
+// recent reference (when the program starts). Of course this isn't much
+// of an issue if scalar is a double-precision number.
+
+static time_t set_reference()
+{
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
+ {
+ return 0;
+ }
+
+ return ts.tv_sec;
+}
+
+static const time_t reference = set_reference();
+
+
+scalar timer::ticks()
+{
+ struct timespec ts;
+
+ int result = clock_gettime(CLOCK_MONOTONIC, &ts);
+ ASSERT(result == 0 && "cannot access clock");
+
+ return scalar(ts.tv_sec - reference) +
+ scalar(ts.tv_nsec) / 1000000000.0;
+}
+
+void timer::sleep(scalar seconds, mode mode)
+{
+ struct timespec ts;
+ int ret;
+
+ if (mode == absolute) seconds -= ticks();
+ ts.tv_sec = time_t(seconds);
+ ts.tv_nsec = long((seconds - scalar(ts.tv_sec)) * 1000000000.0);
+
+ do
+ {
+ ret = nanosleep(&ts, &ts);
+ }
+ while (ret == -1 && errno == EINTR);
+}
+
+
+#else // ! HAVE_CLOCK_GETTIME
+
+
+// If we don't have posix timers, we'll have to use a different timing
+// method. SDL only promises centisecond accuracy, but that's better than
+// a kick in the pants.
+
+scalar timer::ticks()
+{
+ Uint32 ms = SDL_GetTicks();
+ return scalar(ms / 1000) + scalar(ms % 1000) / 1000.0;
+}
+
+void timer::sleep(scalar seconds, mode mode)
+{
+ if (mode == absolute) seconds -= ticks();
+ SDL_Delay(Uint32(clamp(int(seconds * 1000.0), 0, 1000)));
+}
+
+#endif // HAVE_CLOCK_GETTIME
+
+
+} // namespace moof
+