X-Git-Url: https://feistymeow.org/gitweb/?a=blobdiff_plain;f=nucleus%2Flibrary%2Ftimely%2Ftimer_driver.cpp;fp=nucleus%2Flibrary%2Ftimely%2Ftimer_driver.cpp;h=9be8aba0214f532a0458a36d0b370f226c6d5276;hb=457b128b77b5b4a0b7dd3094de543de2ce1477ad;hp=0000000000000000000000000000000000000000;hpb=32d7caf45d886d0d24e69eea00511c7815ac15d0;p=feisty_meow.git diff --git a/nucleus/library/timely/timer_driver.cpp b/nucleus/library/timely/timer_driver.cpp new file mode 100644 index 00000000..9be8aba0 --- /dev/null +++ b/nucleus/library/timely/timer_driver.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef __UNIX__ + #include +#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, + 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 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. +