summaryrefslogtreecommitdiff
path: root/utils/Timer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'utils/Timer.cc')
-rw-r--r--utils/Timer.cc402
1 files changed, 402 insertions, 0 deletions
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 <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+
+/*?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>* 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 <CcuBaseTimer>;
+#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 <CcuBaseTimer> li (*OtherActive);
+ CcuListIterOf <CcuBaseTimer> 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);
+}
+