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, )
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)) {
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");
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),
151 _prompter(new signalling_thread(INITIAL_TIMER_GRANULARITY)),
158 hookup_OS_timer(INITIAL_TIMER_GRANULARITY);
161 // register for the our personal signal.
162 signal(OUR_SIGNAL, &timer_driver_private_handler);
163 _prompter->start(NIL);
167 timer_driver::~timer_driver()
169 #ifdef DEBUG_TIMER_DRIVER
170 FUNCDEF("destructor");
175 struct sigaction action;
176 action.sa_handler = SIG_DFL;
177 action.sa_sigaction = NIL;
178 sigemptyset(&action.sa_mask);
181 action.sa_restorer = NIL;
183 int ret = sigaction(OUR_SIGNAL, &action, NIL);
190 // make sure we aren't still in a timer handler when we reset our list.
195 #ifdef DEBUG_TIMER_DRIVER
196 LOG("waiting to acquire timer_driver lock.");
198 time_control::sleep_ms(PAUSE_TIME);
204 _timers->reset(); // clear out the registered functions.
213 #ifdef DEBUG_TIMER_DRIVER
214 LOG("timer_driver is closing down.");
219 basis::un_int *timer_driver::real_timer_id() { return _real_timer_id; }
222 bool timer_driver::zap_timer(timeable *to_remove)
224 #ifdef DEBUG_TIMER_DRIVER
225 FUNCDEF("zap_timer");
227 #ifdef DEBUG_TIMER_DRIVER
229 LOG("hmmm: zapping timer while handling previous timer...!");
232 auto_synchronizer l(*_lock);
233 int indy = _timers->find_obj(to_remove);
234 if (negative(indy)) return false; // unknown.
235 #ifdef DEBUG_TIMER_DRIVER
236 LOG(a_sprintf("zapping timer %x.", to_remove));
238 driven_object_record *reco = _timers->borrow(indy);
239 reco->_okay_to_invoke = false;
240 if (reco->_handling_timer) {
241 // results are not guaranteed if we see this situation.
242 #ifdef DEBUG_TIMER_DRIVER
243 LOG(a_sprintf("Logic Error: timer %x being zapped WHILE BEING HANDLED!",
250 bool timer_driver::set_timer(int duration, timeable *to_invoke)
252 #ifdef DEBUG_TIMER_DRIVER
253 FUNCDEF("set_timer");
255 LOG("hmmm: setting timer while handling previous timer...!");
258 #ifdef DEBUG_TIMER_DRIVER
259 LOG(a_sprintf("setting timer %x to %d ms.", to_invoke, duration));
261 auto_synchronizer l(*_lock);
262 // find any existing record.
263 int indy = _timers->find_obj(to_invoke);
264 if (negative(indy)) {
265 // add a new record to list.
266 _timers->append(new driven_object_record(duration, to_invoke));
268 // change the existing record.
269 driven_object_record *reco = _timers->borrow(indy);
270 reco->_duration = duration;
271 reco->_okay_to_invoke = true; // just in case.
276 void timer_driver::handle_system_timer()
278 #ifdef DEBUG_TIMER_DRIVER
279 FUNCDEF("handle_system_timer");
282 #ifdef DEBUG_TIMER_DRIVER
283 LOG("terrible error: invoked system timer while handling previous timer.");
289 #ifdef DEBUG_TIMER_DRIVER
290 LOG("into handling OS timer...");
293 array<driven_object_record *> to_invoke_now;
296 // lock the list for a short time, just to put in a stake for the timer
297 // flag; no one is allowed to change the list while this is set to true.
298 auto_synchronizer l(*_lock);
301 // zip across our list and find out which of the timer functions should be
303 for (int i = 0; i < _timers->elements(); i++) {
304 driven_object_record *funky = _timers->borrow(i);
306 const char *msg = "error: timer_driver's timer list logic is broken.";
307 #ifdef DEBUG_TIMER_DRIVER
314 i--; // skip back over dud record.
317 if (funky->_next_hit <= time_stamp()) {
318 // this one needs to be jangled.
319 to_invoke_now += funky;
324 #ifdef DEBUG_TIMER_DRIVER
325 astring pointer_dump;
326 for (int i = 0; i < to_invoke_now.length(); i++) {
327 driven_object_record *funky = to_invoke_now[i];
328 pointer_dump += a_sprintf("%x ", funky->_to_invoke);
330 if (pointer_dump.t())
331 LOG(astring("activating ") + pointer_dump);
334 // now that we have a list of timer functions, let's call on them.
335 for (int i = 0; i < to_invoke_now.length(); i++) {
336 driven_object_record *funky = to_invoke_now[i];
338 auto_synchronizer l(*_lock);
339 if (!funky->_okay_to_invoke) continue; // skip this guy.
340 funky->_handling_timer = true;
342 // call the timer function.
343 funky->_to_invoke->handle_timer_callback();
345 auto_synchronizer l(*_lock);
346 funky->_handling_timer = false;
348 // reset the time for the next hit.
349 funky->_next_hit.reset(funky->_duration);
352 // compute the smallest duration before the next guy should fire.
353 int next_timer_duration = MAX_TIMER_PREDICTION;
354 time_stamp now; // pick a point in time as reference for all timers.
355 for (int i = 0; i < _timers->elements(); i++) {
356 driven_object_record *funky = _timers->borrow(i);
357 int funky_time = int(funky->_next_hit.value() - now.value());
358 // we limit the granularity of timing since we don't want to be raging
359 // on the CPU with too small a duration.
360 if (funky_time < INITIAL_TIMER_GRANULARITY)
361 funky_time = INITIAL_TIMER_GRANULARITY;
362 if (funky_time < next_timer_duration)
363 next_timer_duration = funky_time;
367 // release the timer flag again and do any cleanups that are necessary.
368 auto_synchronizer l(*_lock);
370 for (int i = 0; i < _timers->elements(); i++) {
371 driven_object_record *funky = _timers->borrow(i);
372 if (!funky->_okay_to_invoke) {
373 // clean up something that was unhooked.
380 #ifdef DEBUG_TIMER_DRIVER
381 LOG("done handling OS timer.");
384 // set the next expiration time to the smallest next guy.
385 reset_OS_timer(next_timer_duration);
388 // the following OS_timer methods do not need to lock the mutex, since they
389 // are not actually touching the list of timers.
391 void timer_driver::hookup_OS_timer(int duration)
393 FUNCDEF("hookup_OS_timer");
394 if (negative(duration)) {
395 #ifdef DEBUG_TIMER_DRIVER
396 LOG("seeing negative duration for timer!");
399 } else if (!duration) {
400 #ifdef DEBUG_TIMER_DRIVER
401 LOG("patching zero duration for timer.");
405 #ifdef DEBUG_TIMER_DRIVER
406 LOG(a_sprintf("hooking next OS timer in %d ms.", duration));
409 // just make our thread hit after the duration specified.
410 _prompter->reschedule(duration);
411 #elif defined(__WIN32__)
412 int max_tries_left = 100;
413 while (max_tries_left-- >= 0) {
414 _real_timer_id = (basis::un_int *)SetTimer(NIL, 0, duration,
415 timer_driver_private_handler);
416 if (!_real_timer_id) {
417 // failure to set the timer.
418 LOG("could not set the interval timer.");
419 time_control::sleep_ms(50); // snooze for a bit to see if we can get right.
422 break; // success hooking timer.
427 void timer_driver::unhook_OS_timer()
429 #ifdef DEBUG_TIMER_DRIVER
430 FUNCDEF("unhook_OS_timer");
433 // postpone the thread for quite a while so we can take care of business.
434 _prompter->reschedule(LONG_TIME);
435 #elif defined(__WIN32__)
436 if (_real_timer_id) KillTimer(NIL, (UINT_PTR)_real_timer_id);
438 #ifdef DEBUG_TIMER_DRIVER
439 LOG("unhooked OS timer.");
443 void timer_driver::reset_OS_timer(int next_hit)
445 #ifdef DEBUG_TIMER_DRIVER
446 FUNCDEF("reset_OS_timer");
448 unhook_OS_timer(); // stop the timer from running.
449 hookup_OS_timer(next_hit); // restart the timer with the new interval.