wow. that was easy: git mv core nucleus
[feisty_meow.git] / nucleus / library / timely / timer_driver.cpp
diff --git a/nucleus/library/timely/timer_driver.cpp b/nucleus/library/timely/timer_driver.cpp
new file mode 100644 (file)
index 0000000..9be8aba
--- /dev/null
@@ -0,0 +1,453 @@
+/*
+*  Name   : timer_driver
+*  Author : Chris Koeritz
+
+* Copyright (c) 2003-$now By Author.  This program is free software; you can  *
+* redistribute it and/or modify it under the terms of the GNU General Public  *
+* License as published by the Free Software Foundation; either version 2 of   *
+* the License or (at your option) any later version.  This is online at:      *
+*     http://www.fsf.org/copyleft/gpl.html                                    *
+* Please send any updates to: fred@gruntose.com                               *
+\*****************************************************************************/
+
+#include "timer_driver.h"
+
+#include <application/windoze_helper.h>
+#include <basis/functions.h>
+#include <basis/mutex.h>
+#include <processes/ethread.h>
+#include <structures/amorph.h>
+#include <structures/static_memory_gremlin.h>
+#include <timely/time_control.h>
+#include <timely/time_stamp.h>
+
+#include <signal.h>
+#include <stdio.h>
+#ifdef __UNIX__
+  #include <sys/time.h>
+#endif
+
+using namespace basis;
+using namespace processes;
+using namespace structures;
+using namespace timely;
+
+//#define DEBUG_TIMER_DRIVER
+  // uncomment for noisy code.
+
+#undef LOG
+#define LOG(tpr) printf( (time_stamp::notarize() + "timer_driver::" + func + tpr).s() )
+
+namespace timely {
+
+const int INITIAL_TIMER_GRANULARITY = 14;
+  // the timer will support durations of this length or greater initially.
+  // later durations will be computed based on the timers waiting.
+
+const int MAX_TIMER_PREDICTION = 140;
+  // this is the maximum predictive delay before we wake up again to see if
+  // any new timed items have arrived.  this helps us to not wait too long
+  // when something's scheduled in between snoozes.
+
+const int PAUSE_TIME = 200;
+  // we will pause this many milliseconds if the timer is already occurring
+  // when we're trying to get the lock on our list.
+
+const int LONG_TIME = 1 * HOUR_ms;
+  // the hook can be postponed a really long time with this when necessary.
+
+//////////////
+
+SAFE_STATIC(timer_driver, timer_driver::global_timer_driver, )
+
+//////////////
+
+#ifdef __UNIX__
+const int OUR_SIGNAL = SIGUSR2;
+
+class signalling_thread : public ethread
+{
+public:
+  signalling_thread(int initial_interval) : ethread(initial_interval) {}
+  
+  void perform_activity(void *formal(ptr)) {
+    raise(OUR_SIGNAL);
+  }
+
+private:
+};
+#endif
+
+#ifdef __UNIX__
+void timer_driver_private_handler(int signal_seen)
+#elif defined(__WIN32__)
+void __stdcall timer_driver_private_handler(window_handle hwnd, basis::un_int msg,
+    UINT_PTR id, un_long time)
+#else
+  #error No timer method known for this OS.
+#endif
+{
+#ifdef DEBUG_TIMER_DRIVER
+  #undef static_class_name
+  #define static_class_name() "timer_driver"
+  FUNCDEF("timer_driver_private_handler");
+#endif
+#ifdef __UNIX__
+  int seen = signal_seen;
+  if (seen != OUR_SIGNAL) {
+#elif defined(__WIN32__)
+  basis::un_int *seen = (basis::un_int *)id;
+  if (seen != program_wide_timer().real_timer_id()) {
+#else
+  if (true) {  // unknown OS.
+#endif
+#ifdef DEBUG_TIMER_DRIVER
+    LOG(a_sprintf("unknown signal/message %x caught.", (void *)seen));
+#endif
+    return;
+  }
+  program_wide_timer().handle_system_timer();
+  #undef static_class_name
+}
+
+//////////////
+
+class driven_object_record
+{
+public:
+  int _duration;  // the interval for timer hits on this object.
+  timeable *_to_invoke;  // the object that will be called back.
+  time_stamp _next_hit;  // next time the timer should hit for this object.
+  bool _okay_to_invoke;  // true if this object is okay to call timers on.
+  bool _handling_timer;  // true if we're handling this object right now.
+
+  driven_object_record(int duration, timeable *to_invoke)
+  : _duration(duration), _to_invoke(to_invoke), _next_hit(duration),
+    _okay_to_invoke(true), _handling_timer(false) {}
+};
+
+class driven_objects_list
+: public amorph<driven_object_record>,
+  public virtual root_object
+{
+public:
+  DEFINE_CLASS_NAME("driven_objects_list");
+
+  int find_obj(timeable *obj) {
+    for (int i = 0; i < elements(); i++) {
+      if (borrow(i) && (borrow(i)->_to_invoke == obj))
+        return i;
+    }
+    return common::NOT_FOUND;
+  }
+};
+
+//////////////
+
+timer_driver::timer_driver()
+: _timers(new driven_objects_list),
+  _lock(new mutex),
+#ifdef __UNIX__
+  _prompter(new signalling_thread(INITIAL_TIMER_GRANULARITY)),
+#endif
+#ifdef __WIN32__
+  _real_timer_id(NIL),
+#endif
+  _in_timer(false)
+{
+  hookup_OS_timer(INITIAL_TIMER_GRANULARITY);
+
+#ifdef __UNIX__
+  // register for the our personal signal.
+  signal(OUR_SIGNAL, &timer_driver_private_handler);
+  _prompter->start(NIL);
+#endif
+}
+
+timer_driver::~timer_driver()
+{
+#ifdef DEBUG_TIMER_DRIVER
+  FUNCDEF("destructor");
+#endif
+#ifdef __UNIX__
+  _prompter->stop();
+
+  struct sigaction action;
+  action.sa_handler = SIG_DFL;
+  action.sa_sigaction = NIL;
+  sigemptyset(&action.sa_mask);
+  action.sa_flags = 0;
+#ifndef __APPLE__
+  action.sa_restorer = NIL;
+#endif
+  int ret = sigaction(OUR_SIGNAL, &action, NIL);
+  if (ret) {
+///uhhh
+  }
+#endif
+  unhook_OS_timer();
+
+  // make sure we aren't still in a timer handler when we reset our list.
+  while (true) {
+    _lock->lock();
+    if (_in_timer) {
+      _lock->unlock();
+#ifdef DEBUG_TIMER_DRIVER
+      LOG("waiting to acquire timer_driver lock.");
+#endif
+      time_control::sleep_ms(PAUSE_TIME);
+    } else {
+      break;
+    }
+  }
+
+  _timers->reset();  // clear out the registered functions.
+  _lock->unlock();
+
+  WHACK(_timers);
+  WHACK(_lock);
+#ifdef __UNIX__
+  WHACK(_prompter);
+#endif
+
+#ifdef DEBUG_TIMER_DRIVER
+  LOG("timer_driver is closing down.");
+#endif
+}
+
+#ifdef __WIN32__
+basis::un_int *timer_driver::real_timer_id() { return _real_timer_id; }
+#endif
+
+bool timer_driver::zap_timer(timeable *to_remove)
+{
+#ifdef DEBUG_TIMER_DRIVER
+  FUNCDEF("zap_timer");
+#endif
+#ifdef DEBUG_TIMER_DRIVER
+  if (_in_timer) {
+    LOG("hmmm: zapping timer while handling previous timer...!");
+  }
+#endif
+  auto_synchronizer l(*_lock);
+  int indy = _timers->find_obj(to_remove);
+  if (negative(indy)) return false;  // unknown.
+#ifdef DEBUG_TIMER_DRIVER
+  LOG(a_sprintf("zapping timer %x.", to_remove));
+#endif
+  driven_object_record *reco = _timers->borrow(indy);
+  reco->_okay_to_invoke = false;
+  if (reco->_handling_timer) {
+    // results are not guaranteed if we see this situation.
+#ifdef DEBUG_TIMER_DRIVER
+    LOG(a_sprintf("Logic Error: timer %x being zapped WHILE BEING HANDLED!",
+        to_remove));
+#endif
+  }
+  return true;
+}
+
+bool timer_driver::set_timer(int duration, timeable *to_invoke)
+{
+#ifdef DEBUG_TIMER_DRIVER
+  FUNCDEF("set_timer");
+  if (_in_timer) {
+    LOG("hmmm: setting timer while handling previous timer...!");
+  }
+#endif
+#ifdef DEBUG_TIMER_DRIVER
+  LOG(a_sprintf("setting timer %x to %d ms.", to_invoke, duration));
+#endif
+  auto_synchronizer l(*_lock);
+  // find any existing record.
+  int indy = _timers->find_obj(to_invoke);
+  if (negative(indy)) {
+    // add a new record to list.
+    _timers->append(new driven_object_record(duration, to_invoke));
+  } else {
+    // change the existing record.
+    driven_object_record *reco = _timers->borrow(indy);
+    reco->_duration = duration;
+    reco->_okay_to_invoke = true;  // just in case.
+  }
+  return true;
+}
+
+void timer_driver::handle_system_timer()
+{
+#ifdef DEBUG_TIMER_DRIVER
+  FUNCDEF("handle_system_timer");
+#endif
+  if (_in_timer) {
+#ifdef DEBUG_TIMER_DRIVER
+    LOG("terrible error: invoked system timer while handling previous timer.");
+#endif
+    return;
+  }
+  unhook_OS_timer();
+
+#ifdef DEBUG_TIMER_DRIVER
+  LOG("into handling OS timer...");
+#endif
+
+  array<driven_object_record *> to_invoke_now;
+
+  {
+    // lock the list for a short time, just to put in a stake for the timer
+    // flag; no one is allowed to change the list while this is set to true.
+    auto_synchronizer l(*_lock);
+    _in_timer = true;
+
+    // zip across our list and find out which of the timer functions should be
+    // invoked.
+    for (int i = 0; i < _timers->elements(); i++) {
+      driven_object_record *funky = _timers->borrow(i);
+      if (!funky) {
+        const char *msg = "error: timer_driver's timer list logic is broken.";
+#ifdef DEBUG_TIMER_DRIVER
+        LOG(msg);
+#endif
+#ifdef CATCH_ERRORS
+        throw msg;
+#endif
+        _timers->zap(i, i);
+        i--;  // skip back over dud record.
+        continue;
+      }
+      if (funky->_next_hit <= time_stamp()) {
+        // this one needs to be jangled.
+        to_invoke_now += funky;
+      }
+    }
+  }
+
+#ifdef DEBUG_TIMER_DRIVER
+  astring pointer_dump;
+  for (int i = 0; i < to_invoke_now.length(); i++) {
+    driven_object_record *funky = to_invoke_now[i];
+    pointer_dump += a_sprintf("%x ", funky->_to_invoke);
+  }
+  if (pointer_dump.t())
+    LOG(astring("activating ") + pointer_dump);
+#endif
+
+  // now that we have a list of timer functions, let's call on them.
+  for (int i = 0; i < to_invoke_now.length(); i++) {
+    driven_object_record *funky = to_invoke_now[i];
+    {
+      auto_synchronizer l(*_lock);
+      if (!funky->_okay_to_invoke) continue;  // skip this guy.
+      funky->_handling_timer = true;
+    }
+    // call the timer function.
+    funky->_to_invoke->handle_timer_callback();
+    {
+      auto_synchronizer l(*_lock);
+      funky->_handling_timer = false;
+    }
+    // reset the time for the next hit.
+    funky->_next_hit.reset(funky->_duration);
+  }
+
+  // compute the smallest duration before the next guy should fire.
+  int next_timer_duration = MAX_TIMER_PREDICTION;
+  time_stamp now;  // pick a point in time as reference for all timers.
+  for (int i = 0; i < _timers->elements(); i++) {
+    driven_object_record *funky = _timers->borrow(i);
+    int funky_time = int(funky->_next_hit.value() - now.value());
+    // we limit the granularity of timing since we don't want to be raging
+    // on the CPU with too small a duration.
+    if (funky_time < INITIAL_TIMER_GRANULARITY)
+      funky_time = INITIAL_TIMER_GRANULARITY;
+    if (funky_time < next_timer_duration)
+      next_timer_duration = funky_time;
+  }
+
+  {
+    // release the timer flag again and do any cleanups that are necessary.
+    auto_synchronizer l(*_lock);
+    _in_timer = false;
+    for (int i = 0; i < _timers->elements(); i++) {
+      driven_object_record *funky = _timers->borrow(i);
+      if (!funky->_okay_to_invoke) {
+        // clean up something that was unhooked.
+        _timers->zap(i, i);
+        i--;
+      }
+    }
+  }
+
+#ifdef DEBUG_TIMER_DRIVER
+  LOG("done handling OS timer.");
+#endif
+
+  // set the next expiration time to the smallest next guy.
+  reset_OS_timer(next_timer_duration);
+}
+
+// the following OS_timer methods do not need to lock the mutex, since they
+// are not actually touching the list of timers.
+
+void timer_driver::hookup_OS_timer(int duration)
+{
+  FUNCDEF("hookup_OS_timer");
+  if (negative(duration)) {
+#ifdef DEBUG_TIMER_DRIVER
+    LOG("seeing negative duration for timer!");
+#endif
+    duration = 1;
+  } else if (!duration) {
+#ifdef DEBUG_TIMER_DRIVER
+    LOG("patching zero duration for timer.");
+#endif
+    duration = 1;
+  }
+#ifdef DEBUG_TIMER_DRIVER
+  LOG(a_sprintf("hooking next OS timer in %d ms.", duration));
+#endif
+#ifdef __UNIX__
+  // just make our thread hit after the duration specified.
+  _prompter->reschedule(duration);
+#elif defined(__WIN32__)
+  int max_tries_left = 100;
+  while (max_tries_left-- >= 0) {
+    _real_timer_id = (basis::un_int *)SetTimer(NIL, 0, duration,
+        timer_driver_private_handler);
+    if (!_real_timer_id) {
+      // failure to set the timer.
+      LOG("could not set the interval timer.");
+      time_control::sleep_ms(50);  // snooze for a bit to see if we can get right.
+      continue;
+    } else
+      break;  // success hooking timer.
+  }
+#endif
+}
+
+void timer_driver::unhook_OS_timer()
+{
+#ifdef DEBUG_TIMER_DRIVER
+  FUNCDEF("unhook_OS_timer");
+#endif
+#ifdef __UNIX__
+  // postpone the thread for quite a while so we can take care of business.
+  _prompter->reschedule(LONG_TIME);
+#elif defined(__WIN32__)
+  if (_real_timer_id) KillTimer(NIL, (UINT_PTR)_real_timer_id);
+#endif
+#ifdef DEBUG_TIMER_DRIVER
+  LOG("unhooked OS timer.");
+#endif
+}
+
+void timer_driver::reset_OS_timer(int next_hit)
+{
+#ifdef DEBUG_TIMER_DRIVER
+  FUNCDEF("reset_OS_timer");
+#endif
+  unhook_OS_timer();  // stop the timer from running.
+  hookup_OS_timer(next_hit);  // restart the timer with the new interval.
+}
+
+} //namespace.
+