_TIMED CALLBACKS IN C++_ by Christain Stapfer [LISTING ONE] //===== tmdcbk.hpp -- timedCallback: Interface ====== #ifndef tmdcbk_hpp #define tmdcbk_hpp // -- Maximum number of simultaneously queued callbacks: #define MaxTimedCallbacks 16 // Any number > 0 will do class timedCallback { public: typedef long (*timedCallbackFun)(timedCallback *Entry); // Type of function to be called after a given number of tick() // invocations. // Return value = Number of ticks to wait till next call (0 if // the callback must not be requeued). timedCallback( ); // Constructs a callback using a default 'do-nothing' function. // This allows you to create arrays of timedCallback objects and // define the callback function later by use of member setFun(). timedCallback(timedCallbackFun SomeFun); // Constructs a real-time clock callback to be used for later calls // to queue() and cancel(). void queue(long Delta); // Queues the callback to be called after the specified number of // invocations of member tick(). Delta == 0 cancels the callback. // Raises : NoRoom Callback queue cannot hold another entry. // (Not implemented) // Note : It is ok to requeue an already queued callback. void cancel( ); // Cancels 'this' callback - if it is queued. // Note : It is ok to cancel a callback that is not queued. int isQueued( ) // Returns 1 if 'this' callback is queued, 0 otherwise. { return index != 0; } static void tick( ); // Advances the time by one tick. Queued callbacks may time out and // are invoked from within member tick(). void setFun(timedCallbackFun SomeFun); // Redefines the callback function to be used for 'this' callback. ~timedCallback( ) // Cancels the callback before it goes out of scope.. . { cancel(); } private: timedCallbackFun fun; // Address of the function to be called back. long clock;// System clock count to wait for unsigned index;// Aux. handshake index into the heap // Priority heap of queued callbacks is contained in: static timedCallback *heap[MaxTimedCallbacks + 1]; static unsigned inUse; // Number of invocations of member tick(): static long tickCount; void upHeap( ); // Repositions 'this' heap entry if count member has been decreased void downHeap( ); // Repositions 'this' heap entry if count member has been increased static long defaultFun(timedCallback* ); // Default callback function used by the constructor timedCallback(). // Prevent copying of timedCallback objects: // (You may want to modify this.. ) timedCallback(const timedCallback& ); timedCallback& operator = (const timedCallback& ); };// class timedCallback #endif // ndef tmdcbk_hpp // tmdcbk.hpp [LISTING TWO] //===== timedCallback: Implementation. timedCallback::queue() silently drops //===== queueing requests that cannot be satisfied if the heap is already full. #include "tmdCbk.hpp" // timedCallback interface //#include "exc.hpp" // Exception handling (not used) // static timedCallback data members long timedCallback::tickCount = 0; timedCallback *timedCallback::heap[MaxTimedCallbacks + 1]; unsigned timedCallback::inUse = 0; // timedCallback::*() members long timedCallback::defaultFun(timedCallback* ) // Callback used as a default for the constructor. { return 0;// Don't requeue }// defaultFun() timedCallback::timedCallback( ) { clock = index = 0; fun = timedCallback::defaultFun; }// timedCallback() timedCallback::timedCallback(timedCallbackFun SomeFun) { clock = index = 0; fun = SomeFun; }// timedCallback() void timedCallback::setFun(timedCallbackFun SomeFun) { fun = SomeFun; }// setFun() void timedCallback::queue(long Delta) { long OldClock = clock; if (Delta == 0) { cancel(); } else { clock = tickCount + Delta; if (0 < index) { // Still queued .. if (0 <= clock - OldClock) { downHeap(); } else { upHeap(); } } else if (inUse < MaxTimedCallbacks) { // Not currently queued (and there is room!) index = inUse += 1; heap[inUse] = this; upHeap(); } else { // NoRoom.raise(); Exception handling not available! // You may want to exit() to DOS or something. } } }// queue() void timedCallback::cancel( ) { unsigned Index; Index = index; if (0 < Index) { index = 0; inUse -= 1; if (Index <= inUse) { heap[Index] = heap[inUse + 1]; heap[Index]->downHeap(); } // else cancelling the last entry is trivial .. } }// cancel() void timedCallback::tick( ) { timedCallback **First = heap + 1; long NewTicks; // Advance tick count tickCount += 1; // Deliver timed-out callbacks while (0 < inUse && (*First)->clock == tickCount) { // Expired - deliver! NewTicks = (*(*First)->fun)(*First); if (NewTicks != 0) { // Callback wants to be requeued (*First)->clock = tickCount + NewTicks; } else { // Callback doesn't want to be requeued heap[inUse]->index = 1; (*First)->index = 0; *First = heap[inUse]; inUse -= 1; } if (0 < inUse) { (*First)->downHeap(); } }// while }// tick() // PRIORITY-QUEUE (HEAP) MANAGEMENT void timedCallback::upHeap( ) { unsigned K = index; // Use alias as a sentinel entry to ensure we'll dropout of this loop: heap[0] = this; // Move 'this' up until the heap condition is satisfied again: while (0 < heap[K >> 1]->clock - clock) { heap[K] = heap[K >> 1]; heap[K]->index = K; K >>= 1; } // Actually insert 'this' at its new position heap[K] = this; index = K; }// upHeap() void timedCallback::downHeap( ) { unsigned J, K = index, Kmax = inUse >> 1; // Scan down the heap to locate the new position for 'this': while (K <= Kmax) { J = K << 1; if (J < inUse) { if (heap[J | 1]->clock - heap[J]->clock < 0) { J |= 1; } } if (0 <= heap[J]->clock - clock) break; heap[K] = heap[J]; heap[K]->index = K; K = J; } // Actually insert 'this' at its new position heap[K] = this; index = K; }// downHeap() // tmdcbk.cpp [LISTING THREE] //===== play.cpp -- Example usage of timedCallback objects: Play a tune. //===== Compiled for MS-DOS with Borland C++. #include "tmdcbk.hpp" // Defines timed callbacks #include // We need sound() #include // .. and clock() // -- A sound is defined by the struct: typedef struct { unsigned freq; unsigned delta; } aSound; // More elaborate sounds include fading, up/down sweeps, etc. // To create a tune we must hand it a list of sounds: aSound List[] = { {1000,4},{0,1},{500,3},{0,1},{600,3},{0,1}, {700,1},{0,3},{700,1},{0,3},{700,4},{0,0} }; // A tune is defined as: class tune : timedCallback { static long PlayFun(timedCallback* Self); aSound *toPlay; unsigned nextSound; public: tune(aSound* Tune): timedCallback(tune::PlayFun) { toPlay = Tune; }; void play( ) { nextSound = 0; queue(1); }; void stop( ) { cancel(); }; private: tune();// Only allow tune(aSound*) to be used! }; long tune::PlayFun(timedCallback* Self) //-- Timed callback: Walks down the list of sounds, sets the speaker // and requeues itself accordingly until it reaches delta == 0. { tune *This = (tune *)Self; aSound ThisSound = This->toPlay[This->nextSound]; if (ThisSound.freq == 0) { nosound(); // sound(0) will not do! } else { sound(ThisSound.freq); } if (ThisSound.delta != 0) { This->nextSound += 1; } return ThisSound.delta; }// PlayFun() void DriveTick(unsigned Delta) //-- Aux. function used to avoid fooling around with the clock interrupt. { static clock_t LastClock = 0; for ( ; 0 < Delta ; Delta -= 1) { while (LastClock == clock()) ;// Wait till next clock tick LastClock = clock(); timedCallback::tick(); } }// DriveTick() void main ( ) // Plays a single tune and quits. { tune Tune(List); Tune.play(); // If we had multi-tasking we'd simply do this .. DriveTick(50); // .. without having to drive the callbacks ourselves! }// main() Example 1 timedCallback AutoRepeat(RepeatFun); assuming that you have defined the function: long RepeatFun(timedCallback* Self) { // Eg. generate duplicate keypress.. return NewDelta; } Example 2 class context : timedCallback { // Whatever RepeatFun needs to know! }; long RepeatFun(timedCallback* Self) { context *ContextPtr = (context *)Self; ...// Use ContextPtr->* to access data return NewDelta; } Example 3: void timedCallback::tick() { tickCount += 1; if (0 <= tickCount - nextOut) wakeUp.up(); }