| Posted by
| Nick Gammon
Australia (23,165 posts) Bio
Forum Administrator |
| Message
| As discussed in another thread, here is an example of an event handler.
It is really designed to be called from a "main loop" somewhere (eg. idle loop, after doing a select).
To use it you create an event queue (see below for example), and then use AddEvent (or EventQueue.push) to put events on the queue.
- The queue is a priority queue, so events with the lower "fire" time will be extracted first (as the sense of the priority is reversed, so the lowest time is the highest priority).
- The granularity is 1 millisecond (1/1000 of a second) so you can have fine control over sequence of events.
- If events have the same fire time they are processed in an undefined order, however you can use a sequence argument to force processing in a certain order (the lower sequence number will be processed first).
- To do something useful you simply derive a class from the CEvent class - there are two examples below.
- Events can auto-repeat, so you can have things that happen, say, every 5 seconds. Auto-repeated events are the same event requeued, so any "state" in the derived class is retained.
- Events are automatically counted by the base class, so you can easily see when x events (of a particular instance) have fired.
- There should not be "event creep" - even if processing an event takes a few milliseconds, if an event is repeated it is rescheduled based on when it should have fired, not when it finished processing.
Amended to insert sequence number, to guarantee that events with the same fire time are processed in a designated sequence.
#include <string>
#include <functional>
#include <queue>
#include <vector>
#include <iostream>
// events.h
#ifdef WIN32
#include <windows.h> // for timeGetTime
#else
#include <sys/time.h> // for gettimeofday
#include <unistd.h> // for sleep
#endif
using namespace std;
// thanks to Ksilyan for this routine :)
long GetMillisecondsTime (void)
{
#ifdef WIN32
#pragma comment( lib, "winmm.lib" ) // this is the library it is in
return timeGetTime();
#else
struct timeval resultTimeval;
gettimeofday( &resultTimeval, NULL );
resultTimeval.tv_sec -= 946080000; // 30 years
return resultTimeval.tv_sec * 1000 + resultTimeval.tv_usec / 1000;
#endif
} // end of GetMillisecondsTime
// event object
class CEvent
{
public:
CEvent (const long iMilliseconds = 1000, // default, 1 second
const bool bRepeat = false, // default, no repeat
const long iSequence = 0) : // default, unsequenced
m_iInterval (iMilliseconds), // time interval
m_bRepeat (bRepeat), // whether to keep doing it
m_iWhen (GetMillisecondsTime ()), // set last fired time to now
m_iInstance (0),
m_iSequence (iSequence)
{
Reschedule (); // calculate when it first fires
};
virtual ~CEvent () {}; // important - see Scott Meyers
// operator< (for sorting)
inline bool operator< (const CEvent & rhs) const
{
// we compare > because the sooner events have the
// higher priority
if (m_iWhen == rhs.m_iWhen)
return m_iSequence > rhs.m_iSequence; // sequence order if times same
else
return m_iWhen > rhs.m_iWhen; // time order
};
// call to reschedule it - add interval to last fired time
// so as to avoid event creep
inline void Reschedule (void)
{
m_iWhen += m_iInterval;
};
inline void Fired (void)
{
m_iInstance++;
};
// get values
inline long GetTime (void) const { return m_iWhen; };
inline long GetInterval (void) const { return m_iInterval; };
inline bool Repeat (void) const { return m_bRepeat; };
inline long GetInstance (void) const { return m_iInstance; };
// derive a class and override this to actually do something
virtual void OnEvent (void) = 0;
protected:
long m_iInterval; // seconds until next one
bool m_bRepeat; // true if we are to re-enter in queue
private:
long m_iWhen; // when event fires
long m_iInstance; // how many times it fired
long m_iSequence; // what order to sequence events with the same fire time
}; // end of class CEvent
// for adding events to a priority_queue
struct event_less : binary_function<CEvent*, CEvent*, bool>
{
inline bool operator() (const CEvent* X, const CEvent* Y) const
{
return (*X < *Y);
}
};
typedef priority_queue<CEvent*, vector<CEvent*>, event_less > tEvents;
// event handler
void ProcessEvents (tEvents & EventQueue);
// events.cpp
// my own iterator can be used for inserting into the event queue
class event_queue_back_inserter : public iterator <output_iterator_tag, CEvent*>
{
public:
event_queue_back_inserter (tEvents & events) : m_events (events) {};
// assignment is used to insert into the queue
event_queue_back_inserter & operator= (CEvent* & e)
{
m_events.push (e);
return *this;
};
// dereference and increments are no-ops that return
// the iterator itself
event_queue_back_inserter & operator* () { return *this; };
event_queue_back_inserter & operator++ () { return *this; };
event_queue_back_inserter & operator++ (int) { return *this; };
private:
tEvents & m_events;
}; // end of class event_queue_back_inserter
// 1. pull out events from the event queue
// 2. keep going until one is due in the future
// 3, if rescheduling wanted keep in temporary vector until all are done
void ProcessEvents (tEvents & EventQueue)
{
long now = GetMillisecondsTime ();
vector<CEvent*> repeated_events;
// pull out event that need doing
while (!EventQueue.empty ())
{
CEvent * e = EventQueue.top ();
if (e->GetTime () > now)
break; // not yet
EventQueue.pop (); // remove from queue
e->Fired (); // note it fired
e->OnEvent (); // do the event (derived class)
// if repeating it wanted, recalculate and keep in separate list
if (e->Repeat ())
{
e->Reschedule ();
repeated_events.push_back (e);
}
else
delete e; // otherwise pointer not needed any more
} // end of event loop
// put any event we rescheduled back into the queue
// we do it now so we don'now get into a loop if the event is due to
// fire immediately
copy (repeated_events.begin (),
repeated_events.end (),
event_queue_back_inserter (EventQueue));
} // end of ProcessEvents
// main.cpp
// an instance of an event queue
tEvents EventQueue;
// helper routine to add an event
void AddEvent (CEvent * e)
{
if (e) // being cautious here :)
EventQueue.push (e);
} // end of AddEvent
int main (void)
{
class myevent_A : public CEvent
{
public:
myevent_A (const int iTime,
const string sMsg,
const bool bRepeat = false,
const long iSequence = 0)
: CEvent (iTime, bRepeat, iSequence),
m_sMsg (sMsg)
{ };
virtual void OnEvent (void)
{
cout << m_sMsg << " instance " << GetInstance () << endl;
};
private:
const string m_sMsg;
}; // end of class myevent_A
class myevent_B : public CEvent
{
public:
myevent_B () : CEvent (0, true) { };
virtual void OnEvent (void)
{
cout << "myevent_B, instance " << GetInstance () << endl;
};
}; // end of class myevent_B
AddEvent (new myevent_A (10000, "after 10 secs"));
AddEvent (new myevent_A (5000, "after 5 secs", false, 1));
AddEvent (new myevent_A (5000, "another after 5 secs", false, 2));
AddEvent (new myevent_A (7000, "after 7 secs"));
AddEvent (new myevent_A (2000, "You hear rustling sounds", true));
AddEvent (new myevent_B ());
// main program loop
while (true)
{
ProcessEvents (EventQueue);
// sleep for 1 second - simulate waiting on comms or something
#ifdef WIN32
Sleep (1000);
#else
sleep (1);
#endif
}
return 0;
} // end of main
Example output
myevent_B, instance 1
myevent_B, instance 2
myevent_B, instance 3
You hear rustling sounds instance 1
myevent_B, instance 4
myevent_B, instance 5
You hear rustling sounds instance 2
myevent_B, instance 6
after 5 secs instance 1
another after 5 secs instance 1
myevent_B, instance 7
You hear rustling sounds instance 3
myevent_B, instance 8
after 7 secs instance 1
myevent_B, instance 9
You hear rustling sounds instance 4
myevent_B, instance 10
myevent_B, instance 11
You hear rustling sounds instance 5
after 10 secs instance 1
myevent_B, instance 12
myevent_B, instance 13
You hear rustling sounds instance 6
myevent_B, instance 14
myevent_B, instance 15
You hear rustling sounds instance 7
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|