first check-in of feisty meow codebase. many things broken still due to recent
[feisty_meow.git] / core / library / timely / timer_driver.cpp
1 /*
2 *  Name   : timer_driver
3 *  Author : Chris Koeritz
4
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 \*****************************************************************************/
12
13 #include "timer_driver.h"
14
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>
23
24 #include <signal.h>
25 #include <stdio.h>
26 #ifdef __UNIX__
27   #include <sys/time.h>
28 #endif
29
30 using namespace basis;
31 using namespace processes;
32 using namespace structures;
33 using namespace timely;
34
35 //#define DEBUG_TIMER_DRIVER
36   // uncomment for noisy code.
37
38 #undef LOG
39 #define LOG(tpr) printf( (time_stamp::notarize() + "timer_driver::" + func + tpr).s() )
40
41 namespace timely {
42
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.
46
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.
51
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.
55
56 const int LONG_TIME = 1 * HOUR_ms;
57   // the hook can be postponed a really long time with this when necessary.
58
59 //////////////
60
61 SAFE_STATIC(timer_driver, timer_driver::global_timer_driver, )
62
63 //////////////
64
65 #ifdef __UNIX__
66 const int OUR_SIGNAL = SIGUSR2;
67
68 class signalling_thread : public ethread
69 {
70 public:
71   signalling_thread(int initial_interval) : ethread(initial_interval) {}
72   
73   void perform_activity(void *formal(ptr)) {
74     raise(OUR_SIGNAL);
75   }
76
77 private:
78 };
79 #endif
80
81 #ifdef __UNIX__
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)
86 #else
87   #error No timer method known for this OS.
88 #endif
89 {
90 #ifdef DEBUG_TIMER_DRIVER
91   #undef static_class_name
92   #define static_class_name() "timer_driver"
93   FUNCDEF("timer_driver_private_handler");
94 #endif
95 #ifdef __UNIX__
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()) {
101 #else
102   if (true) {  // unknown OS.
103 #endif
104 #ifdef DEBUG_TIMER_DRIVER
105     LOG(a_sprintf("unknown signal/message %x caught.", (void *)seen));
106 #endif
107     return;
108   }
109   program_wide_timer().handle_system_timer();
110   #undef static_class_name
111 }
112
113 //////////////
114
115 class driven_object_record
116 {
117 public:
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.
123
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) {}
127 };
128
129 class driven_objects_list
130 : public amorph<driven_object_record>,
131   public virtual root_object
132 {
133 public:
134   DEFINE_CLASS_NAME("driven_objects_list");
135
136   int find_obj(timeable *obj) {
137     for (int i = 0; i < elements(); i++) {
138       if (borrow(i) && (borrow(i)->_to_invoke == obj))
139         return i;
140     }
141     return common::NOT_FOUND;
142   }
143 };
144
145 //////////////
146
147 timer_driver::timer_driver()
148 : _timers(new driven_objects_list),
149   _lock(new mutex),
150 #ifdef __UNIX__
151   _prompter(new signalling_thread(INITIAL_TIMER_GRANULARITY)),
152 #endif
153 #ifdef __WIN32__
154   _real_timer_id(NIL),
155 #endif
156   _in_timer(false)
157 {
158   hookup_OS_timer(INITIAL_TIMER_GRANULARITY);
159
160 #ifdef __UNIX__
161   // register for the our personal signal.
162   signal(OUR_SIGNAL, &timer_driver_private_handler);
163   _prompter->start(NIL);
164 #endif
165 }
166
167 timer_driver::~timer_driver()
168 {
169 #ifdef DEBUG_TIMER_DRIVER
170   FUNCDEF("destructor");
171 #endif
172 #ifdef __UNIX__
173   _prompter->stop();
174
175   struct sigaction action;
176   action.sa_handler = SIG_DFL;
177   action.sa_sigaction = NIL;
178   sigemptyset(&action.sa_mask);
179   action.sa_flags = 0;
180 #ifndef __APPLE__
181   action.sa_restorer = NIL;
182 #endif
183   int ret = sigaction(OUR_SIGNAL, &action, NIL);
184   if (ret) {
185 ///uhhh
186   }
187 #endif
188   unhook_OS_timer();
189
190   // make sure we aren't still in a timer handler when we reset our list.
191   while (true) {
192     _lock->lock();
193     if (_in_timer) {
194       _lock->unlock();
195 #ifdef DEBUG_TIMER_DRIVER
196       LOG("waiting to acquire timer_driver lock.");
197 #endif
198       time_control::sleep_ms(PAUSE_TIME);
199     } else {
200       break;
201     }
202   }
203
204   _timers->reset();  // clear out the registered functions.
205   _lock->unlock();
206
207   WHACK(_timers);
208   WHACK(_lock);
209 #ifdef __UNIX__
210   WHACK(_prompter);
211 #endif
212
213 #ifdef DEBUG_TIMER_DRIVER
214   LOG("timer_driver is closing down.");
215 #endif
216 }
217
218 #ifdef __WIN32__
219 basis::un_int *timer_driver::real_timer_id() { return _real_timer_id; }
220 #endif
221
222 bool timer_driver::zap_timer(timeable *to_remove)
223 {
224 #ifdef DEBUG_TIMER_DRIVER
225   FUNCDEF("zap_timer");
226 #endif
227 #ifdef DEBUG_TIMER_DRIVER
228   if (_in_timer) {
229     LOG("hmmm: zapping timer while handling previous timer...!");
230   }
231 #endif
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));
237 #endif
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!",
244         to_remove));
245 #endif
246   }
247   return true;
248 }
249
250 bool timer_driver::set_timer(int duration, timeable *to_invoke)
251 {
252 #ifdef DEBUG_TIMER_DRIVER
253   FUNCDEF("set_timer");
254   if (_in_timer) {
255     LOG("hmmm: setting timer while handling previous timer...!");
256   }
257 #endif
258 #ifdef DEBUG_TIMER_DRIVER
259   LOG(a_sprintf("setting timer %x to %d ms.", to_invoke, duration));
260 #endif
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));
267   } else {
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.
272   }
273   return true;
274 }
275
276 void timer_driver::handle_system_timer()
277 {
278 #ifdef DEBUG_TIMER_DRIVER
279   FUNCDEF("handle_system_timer");
280 #endif
281   if (_in_timer) {
282 #ifdef DEBUG_TIMER_DRIVER
283     LOG("terrible error: invoked system timer while handling previous timer.");
284 #endif
285     return;
286   }
287   unhook_OS_timer();
288
289 #ifdef DEBUG_TIMER_DRIVER
290   LOG("into handling OS timer...");
291 #endif
292
293   array<driven_object_record *> to_invoke_now;
294
295   {
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);
299     _in_timer = true;
300
301     // zip across our list and find out which of the timer functions should be
302     // invoked.
303     for (int i = 0; i < _timers->elements(); i++) {
304       driven_object_record *funky = _timers->borrow(i);
305       if (!funky) {
306         const char *msg = "error: timer_driver's timer list logic is broken.";
307 #ifdef DEBUG_TIMER_DRIVER
308         LOG(msg);
309 #endif
310 #ifdef CATCH_ERRORS
311         throw msg;
312 #endif
313         _timers->zap(i, i);
314         i--;  // skip back over dud record.
315         continue;
316       }
317       if (funky->_next_hit <= time_stamp()) {
318         // this one needs to be jangled.
319         to_invoke_now += funky;
320       }
321     }
322   }
323
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);
329   }
330   if (pointer_dump.t())
331     LOG(astring("activating ") + pointer_dump);
332 #endif
333
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];
337     {
338       auto_synchronizer l(*_lock);
339       if (!funky->_okay_to_invoke) continue;  // skip this guy.
340       funky->_handling_timer = true;
341     }
342     // call the timer function.
343     funky->_to_invoke->handle_timer_callback();
344     {
345       auto_synchronizer l(*_lock);
346       funky->_handling_timer = false;
347     }
348     // reset the time for the next hit.
349     funky->_next_hit.reset(funky->_duration);
350   }
351
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;
364   }
365
366   {
367     // release the timer flag again and do any cleanups that are necessary.
368     auto_synchronizer l(*_lock);
369     _in_timer = false;
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.
374         _timers->zap(i, i);
375         i--;
376       }
377     }
378   }
379
380 #ifdef DEBUG_TIMER_DRIVER
381   LOG("done handling OS timer.");
382 #endif
383
384   // set the next expiration time to the smallest next guy.
385   reset_OS_timer(next_timer_duration);
386 }
387
388 // the following OS_timer methods do not need to lock the mutex, since they
389 // are not actually touching the list of timers.
390
391 void timer_driver::hookup_OS_timer(int duration)
392 {
393   FUNCDEF("hookup_OS_timer");
394   if (negative(duration)) {
395 #ifdef DEBUG_TIMER_DRIVER
396     LOG("seeing negative duration for timer!");
397 #endif
398     duration = 1;
399   } else if (!duration) {
400 #ifdef DEBUG_TIMER_DRIVER
401     LOG("patching zero duration for timer.");
402 #endif
403     duration = 1;
404   }
405 #ifdef DEBUG_TIMER_DRIVER
406   LOG(a_sprintf("hooking next OS timer in %d ms.", duration));
407 #endif
408 #ifdef __UNIX__
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.
420       continue;
421     } else
422       break;  // success hooking timer.
423   }
424 #endif
425 }
426
427 void timer_driver::unhook_OS_timer()
428 {
429 #ifdef DEBUG_TIMER_DRIVER
430   FUNCDEF("unhook_OS_timer");
431 #endif
432 #ifdef __UNIX__
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);
437 #endif
438 #ifdef DEBUG_TIMER_DRIVER
439   LOG("unhooked OS timer.");
440 #endif
441 }
442
443 void timer_driver::reset_OS_timer(int next_hit)
444 {
445 #ifdef DEBUG_TIMER_DRIVER
446   FUNCDEF("reset_OS_timer");
447 #endif
448   unhook_OS_timer();  // stop the timer from running.
449   hookup_OS_timer(next_hit);  // restart the timer with the new interval.
450 }
451
452 } //namespace.
453