3 * Author : Chris Koeritz
5 * Copyright (c) 2003-$now By Author. This program is free software; you can *
6 * redistribute it and/or modify it under the terms of the GNU General Public *
7 * License as published by the Free Software Foundation; either version 2 of *
8 * the License or (at your option) any later version. This is online at: *
9 * http://www.fsf.org/copyleft/gpl.html *
10 * Please send any updates to: fred@gruntose.com *
11 \*****************************************************************************/
13 #include "timer_driver.h"
15 #include <application/windoze_helper.h>
16 #include <basis/functions.h>
17 #include <basis/mutex.h>
18 #include <processes/ethread.h>
19 #include <structures/amorph.h>
20 #include <structures/static_memory_gremlin.h>
21 #include <timely/time_control.h>
22 #include <timely/time_stamp.h>
30 using namespace basis;
31 using namespace processes;
32 using namespace structures;
33 using namespace timely;
35 //#define DEBUG_TIMER_DRIVER
36 // uncomment for noisy code.
39 #define LOG(tpr) printf( (time_stamp::notarize() + "timer_driver::" + func + tpr).s() )
43 const int INITIAL_TIMER_GRANULARITY = 14;
44 // the timer will support durations of this length or greater initially.
45 // later durations will be computed based on the timers waiting.
47 const int MAX_TIMER_PREDICTION = 140;
48 // this is the maximum predictive delay before we wake up again to see if
49 // any new timed items have arrived. this helps us to not wait too long
50 // when something's scheduled in between snoozes.
52 const int PAUSE_TIME = 200;
53 // we will pause this many milliseconds if the timer is already occurring
54 // when we're trying to get the lock on our list.
56 const int LONG_TIME = 1 * HOUR_ms;
57 // the hook can be postponed a really long time with this when necessary.
61 SAFE_STATIC(timer_driver, timer_driver::global_timer_driver, )
65 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
66 const int OUR_SIGNAL = SIGUSR2;
68 class signalling_thread : public ethread
71 signalling_thread(int initial_interval) : ethread(initial_interval) {}
73 void perform_activity(void *formal(ptr)) {
81 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
82 void timer_driver_private_handler(int signal_seen)
83 #elif defined(__WIN32__)
84 void __stdcall timer_driver_private_handler(window_handle hwnd, basis::un_int msg,
85 UINT_PTR id, un_long time)
87 #error No timer method known for this OS.
90 #ifdef DEBUG_TIMER_DRIVER
91 #undef static_class_name
92 #define static_class_name() "timer_driver"
93 FUNCDEF("timer_driver_private_handler");
95 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
96 int seen = signal_seen;
97 if (seen != OUR_SIGNAL) {
98 #elif defined(__WIN32__)
99 basis::un_int *seen = (basis::un_int *)id;
100 if (seen != program_wide_timer().real_timer_id()) {
102 if (true) { // unknown OS.
104 #ifdef DEBUG_TIMER_DRIVER
105 LOG(a_sprintf("unknown signal/message %x caught.", (void *)seen));
109 program_wide_timer().handle_system_timer();
110 #undef static_class_name
115 class driven_object_record
118 int _duration; // the interval for timer hits on this object.
119 timeable *_to_invoke; // the object that will be called back.
120 time_stamp _next_hit; // next time the timer should hit for this object.
121 bool _okay_to_invoke; // true if this object is okay to call timers on.
122 bool _handling_timer; // true if we're handling this object right now.
124 driven_object_record(int duration, timeable *to_invoke)
125 : _duration(duration), _to_invoke(to_invoke), _next_hit(duration),
126 _okay_to_invoke(true), _handling_timer(false) {}
129 class driven_objects_list
130 : public amorph<driven_object_record>,
131 public virtual root_object
134 DEFINE_CLASS_NAME("driven_objects_list");
136 int find_obj(timeable *obj) {
137 for (int i = 0; i < elements(); i++) {
138 if (borrow(i) && (borrow(i)->_to_invoke == obj))
141 return common::NOT_FOUND;
147 timer_driver::timer_driver()
148 : _timers(new driven_objects_list),
150 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
151 _prompter(new signalling_thread(INITIAL_TIMER_GRANULARITY)),
153 _real_timer_id(NULL_POINTER),
157 hookup_OS_timer(INITIAL_TIMER_GRANULARITY);
160 // register for the our personal signal.
161 signal(OUR_SIGNAL, &timer_driver_private_handler);
162 _prompter->start(NULL_POINTER);
166 timer_driver::~timer_driver()
168 #ifdef DEBUG_TIMER_DRIVER
169 FUNCDEF("destructor");
174 struct sigaction action;
175 action.sa_handler = SIG_DFL;
176 action.sa_sigaction = NULL_POINTER;
177 sigemptyset(&action.sa_mask);
180 action.sa_restorer = NULL_POINTER;
182 int ret = sigaction(OUR_SIGNAL, &action, NULL_POINTER);
189 // make sure we aren't still in a timer handler when we reset our list.
194 #ifdef DEBUG_TIMER_DRIVER
195 LOG("waiting to acquire timer_driver lock.");
197 time_control::sleep_ms(PAUSE_TIME);
203 _timers->reset(); // clear out the registered functions.
212 #ifdef DEBUG_TIMER_DRIVER
213 LOG("timer_driver is closing down.");
218 basis::un_int *timer_driver::real_timer_id() { return _real_timer_id; }
221 bool timer_driver::zap_timer(timeable *to_remove)
223 #ifdef DEBUG_TIMER_DRIVER
224 FUNCDEF("zap_timer");
226 #ifdef DEBUG_TIMER_DRIVER
228 LOG("hmmm: zapping timer while handling previous timer...!");
231 auto_synchronizer l(*_lock);
232 int indy = _timers->find_obj(to_remove);
233 if (negative(indy)) return false; // unknown.
234 #ifdef DEBUG_TIMER_DRIVER
235 LOG(a_sprintf("zapping timer %x.", to_remove));
237 driven_object_record *reco = _timers->borrow(indy);
238 reco->_okay_to_invoke = false;
239 if (reco->_handling_timer) {
240 // results are not guaranteed if we see this situation.
241 #ifdef DEBUG_TIMER_DRIVER
242 LOG(a_sprintf("Logic Error: timer %x being zapped WHILE BEING HANDLED!",
249 bool timer_driver::set_timer(int duration, timeable *to_invoke)
251 #ifdef DEBUG_TIMER_DRIVER
252 FUNCDEF("set_timer");
254 LOG("hmmm: setting timer while handling previous timer...!");
257 #ifdef DEBUG_TIMER_DRIVER
258 LOG(a_sprintf("setting timer %x to %d ms.", to_invoke, duration));
260 auto_synchronizer l(*_lock);
261 // find any existing record.
262 int indy = _timers->find_obj(to_invoke);
263 if (negative(indy)) {
264 // add a new record to list.
265 _timers->append(new driven_object_record(duration, to_invoke));
267 // change the existing record.
268 driven_object_record *reco = _timers->borrow(indy);
269 reco->_duration = duration;
270 reco->_okay_to_invoke = true; // just in case.
275 void timer_driver::handle_system_timer()
277 #ifdef DEBUG_TIMER_DRIVER
278 FUNCDEF("handle_system_timer");
281 #ifdef DEBUG_TIMER_DRIVER
282 LOG("terrible error: invoked system timer while handling previous timer.");
288 #ifdef DEBUG_TIMER_DRIVER
289 LOG("into handling OS timer...");
292 array<driven_object_record *> to_invoke_now;
295 // lock the list for a short time, just to put in a stake for the timer
296 // flag; no one is allowed to change the list while this is set to true.
297 auto_synchronizer l(*_lock);
300 // zip across our list and find out which of the timer functions should be
302 for (int i = 0; i < _timers->elements(); i++) {
303 driven_object_record *funky = _timers->borrow(i);
305 const char *msg = "error: timer_driver's timer list logic is broken.";
306 #ifdef DEBUG_TIMER_DRIVER
313 i--; // skip back over dud record.
316 if (funky->_next_hit <= time_stamp()) {
317 // this one needs to be jangled.
318 to_invoke_now += funky;
323 #ifdef DEBUG_TIMER_DRIVER
324 astring pointer_dump;
325 for (int i = 0; i < to_invoke_now.length(); i++) {
326 driven_object_record *funky = to_invoke_now[i];
327 pointer_dump += a_sprintf("%x ", funky->_to_invoke);
329 if (pointer_dump.t())
330 LOG(astring("activating ") + pointer_dump);
333 // now that we have a list of timer functions, let's call on them.
334 for (int i = 0; i < to_invoke_now.length(); i++) {
335 driven_object_record *funky = to_invoke_now[i];
337 auto_synchronizer l(*_lock);
338 if (!funky->_okay_to_invoke) continue; // skip this guy.
339 funky->_handling_timer = true;
341 // call the timer function.
342 funky->_to_invoke->handle_timer_callback();
344 auto_synchronizer l(*_lock);
345 funky->_handling_timer = false;
347 // reset the time for the next hit.
348 funky->_next_hit.reset(funky->_duration);
351 // compute the smallest duration before the next guy should fire.
352 int next_timer_duration = MAX_TIMER_PREDICTION;
353 time_stamp now; // pick a point in time as reference for all timers.
354 for (int i = 0; i < _timers->elements(); i++) {
355 driven_object_record *funky = _timers->borrow(i);
356 int funky_time = int(funky->_next_hit.value() - now.value());
357 // we limit the granularity of timing since we don't want to be raging
358 // on the CPU with too small a duration.
359 if (funky_time < INITIAL_TIMER_GRANULARITY)
360 funky_time = INITIAL_TIMER_GRANULARITY;
361 if (funky_time < next_timer_duration)
362 next_timer_duration = funky_time;
366 // release the timer flag again and do any cleanups that are necessary.
367 auto_synchronizer l(*_lock);
369 for (int i = 0; i < _timers->elements(); i++) {
370 driven_object_record *funky = _timers->borrow(i);
371 if (!funky->_okay_to_invoke) {
372 // clean up something that was unhooked.
379 #ifdef DEBUG_TIMER_DRIVER
380 LOG("done handling OS timer.");
383 // set the next expiration time to the smallest next guy.
384 reset_OS_timer(next_timer_duration);
387 // the following OS_timer methods do not need to lock the mutex, since they
388 // are not actually touching the list of timers.
390 void timer_driver::hookup_OS_timer(int duration)
392 FUNCDEF("hookup_OS_timer");
393 if (negative(duration)) {
394 #ifdef DEBUG_TIMER_DRIVER
395 LOG("seeing negative duration for timer!");
398 } else if (!duration) {
399 #ifdef DEBUG_TIMER_DRIVER
400 LOG("patching zero duration for timer.");
404 #ifdef DEBUG_TIMER_DRIVER
405 LOG(a_sprintf("hooking next OS timer in %d ms.", duration));
407 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
408 // just make our thread hit after the duration specified.
409 _prompter->reschedule(duration);
410 #elif defined(_MSC_VER)
411 int max_tries_left = 100;
412 while (max_tries_left-- >= 0) {
413 _real_timer_id = (basis::un_int *)SetTimer(NULL_POINTER, 0, duration,
414 timer_driver_private_handler);
415 if (!_real_timer_id) {
416 // failure to set the timer.
417 LOG("could not set the interval timer.");
418 time_control::sleep_ms(50); // snooze for a bit to see if we can get right.
421 break; // success hooking timer.
426 void timer_driver::unhook_OS_timer()
428 #ifdef DEBUG_TIMER_DRIVER
429 FUNCDEF("unhook_OS_timer");
431 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
432 // postpone the thread for quite a while so we can take care of business.
433 _prompter->reschedule(LONG_TIME);
434 #elif defined(_MSC_VER)
435 if (_real_timer_id) KillTimer(NULL_POINTER, (UINT_PTR)_real_timer_id);
437 #ifdef DEBUG_TIMER_DRIVER
438 LOG("unhooked OS timer.");
442 void timer_driver::reset_OS_timer(int next_hit)
444 #ifdef DEBUG_TIMER_DRIVER
445 FUNCDEF("reset_OS_timer");
447 unhook_OS_timer(); // stop the timer from running.
448 hookup_OS_timer(next_hit); // restart the timer with the new interval.