From 3a4838bed13b767132cbdf06364b2658da6cc356 Mon Sep 17 00:00:00 2001 From: chatty Date: Tue, 15 Dec 1992 10:55:33 +0000 Subject: Initial revision --- utils/Timer.cc | 402 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 utils/Timer.cc (limited to 'utils/Timer.cc') diff --git a/utils/Timer.cc b/utils/Timer.cc new file mode 100644 index 0000000..7911f9a --- /dev/null +++ b/utils/Timer.cc @@ -0,0 +1,402 @@ +/* + * CENA C++ Utilities + * + * by Stephane Chatty + * + * Copyright 1992 + * Centre d'Etudes de la Navigation Aerienne (CENA) + * + * timers + * + * $Id$ + * $CurLog$ + */ + +#include "Timer.h" +#include "Signal.h" +#include "List.h" + +#include +#include +#include +#include +#include + +/*?class CcuBaseTimer +The class \typ{CcuBaseTimer} is provided as a base class for timers. +It comes with a derived class \typ{CcuTimer} that can immediately be used. +A timer is created with a period expressed in milliseconds. It will then +periodically call the member function \fun{Handle}, which should be redefined +in derived classes. + +Please note that timers use the signal \var{SIGALRM} and the interval timers +provided by UNIX. +Using them concurrently with system calls such as \fun{sleep} yields +unexpected results. + +The member function \fun{Handle} has a time parameter: it is passed the +current (absolute) time when the signal was received from the system. However, +several timers may be triggered at the same time. +If one of them has a slow \fun{Handle}, the time passed to the next ones no more +corrresponds to the current time. If you want to know the exact time, and you are +not sure of the behaviour of the other timers, it is preferable to use a \typ{CcuTimeStamp}. +?*/ + +/*!class CcuBaseTimer +The implementation of CcuBaseTimer is a bit tricky, because it tries to be fast. +Here is the basic idea: active timers are stored in a list, sorted by increasing time-out +times. When a signal occurs, all timers whose time-out time is smaller than the current +time are notified and removed from the list, then they are inserted again. +Then, the optimizations. First, we distinguish the first active timer from the others. +It is not in the list, but has a pointer of its own. Second, we do not remove timers +from the list when they are stopped, but rather flag them as inactive. It means that +we have to test a flag when extracting timers from the list. It also means that we can +have many occurences of the same timer in the list, only one being in the right place. +Even worse, when such a timer is inserted again in the list, there is no way of knowing +that the other occurences are wrong: it is the same object, and it no longer flagged as +inactive. However, if we remove all encountered inactive timers when inserting one, +and if we make sure that a timer is flagged as inactive when we insert it, all wrong +occurences before the right one will be removed. +!*/ + +CcuSignalHandler* CcuBaseTimer::TimeOutHandler = 0; +CcuBaseTimer* CcuBaseTimer::FirstActive = 0; +#ifndef CPLUS_BUG19 +CcuListOf * CcuBaseTimer::OtherActive = 0; +#else +CcuList* CcuBaseTimer::OtherActive = 0; +#endif + +/*?nodoc?*/ +void +CcuBaseTimer :: ClassInit () +{ + TimeOutHandler = new CcuSignalHandler (SigAlrm, &CcuBaseTimer::HandleSignal); +#ifndef CPLUS_BUG19 + OtherActive = new CcuListOf ; +#else + OtherActive = new CcuList; +#endif +} + +/*?hidden?*/ +void +CcuBaseTimer :: StopAlarm () +{ + struct itimerval itval; + timerclear (&itval.it_value); + timerclear (&itval.it_interval); + setitimer (ITIMER_REAL, &itval, 0); +} + +/*?hidden?*/ +void +CcuBaseTimer :: SetAlarm (Millisecond delay) +{ + if (delay <= 0) { + StopAlarm (); + kill (getpid (), SigAlrm); + return; + } + + struct itimerval itval; + timerclear (&itval.it_interval); + itval.it_value.tv_sec = delay / 1000; + itval.it_value.tv_usec = 1000 * (delay % 1000); + setitimer (ITIMER_REAL, &itval, 0); +} + +/*! +Remove and return the first active timer in the active timer list. +Timers in that list can actually be inactive. +!*/ +/*?hidden?*/ +CcuBaseTimer* +CcuBaseTimer :: ExtractNextActive () +{ + CcuBaseTimer* t; + + /* all entries of OtherActive are valid pointers, + hence t == 0 iff OtherActive is empty */ +#ifndef CPLUS_BUG19 + while ((t = OtherActive->RemoveFirst ()) && (t->Status != Active)) +#else + while ((t = (CcuBaseTimer*) OtherActive->RemoveFirst ()) && (t->Status != Active)) +#endif + ; + return t; +} + +#ifndef CPLUS_BUG19 + +/*?hidden?*/ +int +CcuBaseTimer :: IsInactive (CcuBaseTimer* t) +{ + return (t->Status != Active); +} + +#else + +/*?hidden?*/ +int +CcuBaseTimer :: IsInactive (CcuListItem* t) +{ + return (((CcuBaseTimer*) t)->Status != Active); +} + +#endif /* CPLUS_BUG19 */ + + +/*! +This function is called by the signal handler. The first active timer is expired +and scheduled again. Then all the timers whose expiration time is earlier +that the current time are removed from the list, expired and scheduled again. +The first non-expired timer, if any, is removed from the list and saved +as the first active timer. +Finally, the alarm is set up to expire at the expiration time of the new first +active timer. +!*/ +/*?hidden?*/ +void +CcuBaseTimer :: HandleSignal (int) +{ + CcuTimeStamp now; + + /* Handle the first timer */ + FirstActive->Reschedule (); + FirstActive->Handle (now); + + /* handle all other expired timers. */ + CcuBaseTimer* t; + while (t = ExtractNextActive ()) { + /* Problem : if one Handle () is long, "now" will then be wrong. */ + if (t->NextDate > now) + break; + t->Reschedule (); + t->Handle (now); + } + + CcuTimeStamp then; + if (t) + SetAlarm (t->NextDate - then); + + + FirstActive = t; +} + + +/*? +Create a timer that will send a signal every \var{period} milliseconds, \var{pulses} times. +If \var{pulses} is negative, the timer will send signals forever. +Timers are activated at creation time. They are disactivated, but not destroyed, after +their last signal. +?*/ +CcuBaseTimer :: CcuBaseTimer (Millisecond period, int pulses) +: Status (Active), + Period (period), + PulsesLeft (pulses) +{ + CcuSignalBlocker b (SigAlrm); + + if (!TimeOutHandler) + ClassInit (); + if (PulsesLeft != 0) + Activate (); +} + + +/*?hidden?*/ +CcuBaseTimer :: ~CcuBaseTimer () +{ + /* stop it */ + if (Status == Active) + Stop (); + + /* Remove all entries pointing to inactive timers, including this one */ + OtherActive->Remove (IsInactive, CcuList::All); +} + +/*? +Change the period of a timer. The new period will not be taken into account +before the next time-out. +?*/ +void +CcuBaseTimer :: ChangePeriod (Millisecond period) +{ + CcuSignalBlocker b (SigAlrm); + Period = period; +} + +/*? +Stop this timer if it was running, then start it with its current period. +This function can be used to reset a timer to the beginning of a period. +?*/ +void +CcuBaseTimer :: Restart () +{ + if (PulsesLeft == 0) + return; + + /* this function could be optimized: sometimes SetAlarm is called twice. */ + CcuSignalBlocker b (SigAlrm); + if (Status == Active) + Stop (); + Activate (); +} + +/*! +This function sets up a timer that was previously inactive. +The timer is inserted in the list of active timers, and if it is the +first one, the alarm is updated. +!*/ +/*?hidden?*/ +void +CcuBaseTimer :: Activate () +{ + CcuTimeStamp now; + NextDate = now + Period; + + if (!FirstActive) { + FirstActive = this; + SetAlarm (Period); + } else if (NextDate < FirstActive->NextDate ) { + SetAlarm (Period); + OtherActive->Prepend (FirstActive); + FirstActive = this; + } else + Schedule (NextDate); + Status = Active; +} + +/*?hidden?*/ +void +CcuBaseTimer :: Reschedule () +{ + if (PulsesLeft == 0) // this should not happen... + return; + + if (PulsesLeft > 0) + if (--PulsesLeft == 0) + return; + + Schedule (NextDate + Period); +} + +/*?hidden?*/ +void +CcuBaseTimer :: Schedule (Millisecond when) +{ + NextDate = when; + + /* temporarily set status to inactive so that obsolete entries + in OtherActive pointing to this timer can be removed */ + Status = Inactive; + +#ifndef CPLUS_BUG19 + CcuListIterOf li (*OtherActive); + CcuListIterOf lj (*OtherActive); +#else + CcuListIter li (*OtherActive); + CcuListIter lj (*OtherActive); +#endif + + while (++li) { + /* while we're at it, remove inactive timers from the list */ +#ifndef CPLUS_BUG19 + CcuBaseTimer* cur = *li; +#else + CcuBaseTimer* cur = (CcuBaseTimer*) *li; +#endif + if (cur->Status != Active) { + OtherActive->RemoveAfter (lj); + li = lj; + } else if (cur->NextDate < NextDate) + ++lj; + else + break; + } + OtherActive->InsertAfter (lj, this); + Status = Active; +} + +/*? +Stop a timer. +This timer will not deliver any signal until \fun{Restart} is called. +?*/ +void +CcuBaseTimer :: Stop () +{ + if (Status != Active) + return; + + CcuSignalBlocker b (SigAlrm); + + Status = Inactive; + + /* if this timer was the first active one, find another one to replace it */ + if (this == FirstActive) { + FirstActive = ExtractNextActive (); + if (FirstActive == 0) + StopAlarm (); + else { + CcuTimeStamp now; + SetAlarm (FirstActive->NextDate -now); + } + } +} + +/*? +Wait for this timer to expire. If it is stopped, return immediately. +?*/ +void +CcuBaseTimer :: Wait () +{ + if (Status != Active) + return; + + Millisecond next_date = NextDate; + for (;;) { + if (wait (0) >= 0) // not an interrupt + continue; + if (Status != Active || next_date != NextDate) + return; + } +} + +/*?hidden?*/ +void +CcuBaseTimer :: Handle (Millisecond) +{ +} + +/*?class CcuTimer +The class \typ{CcuTimer} is a derived class of \typ{CcuBaseTimer} that +can be used without deriving a new class. +Each \typ{CcuTimer} holds a pointer to a function which is called when the timer +expires. This function, which is passed to the constructor, must +take a \typ{Millisecond} argument and return \typ{void}. +?*/ + +/*? +Create a timer that will expire every \var{period} milliseconds and call +the function \var{handler}. +?*/ +CcuTimer :: CcuTimer (Millisecond period, void (*handler) (Millisecond), int pulses) +: CcuBaseTimer (period, pulses), + Handler (handler) +{ +} + +/*?nodoc?*/ +CcuTimer :: ~CcuTimer () +{ +} + +/*?nodoc?*/ +void +CcuTimer :: Handle (Millisecond ref) +{ + (*Handler) (ref); +} + -- cgit v1.1