A solution often adopted is to write the application in the style of an event driven system. That is, something happening and to which a task should respond, is modelled as an event. The event is put together by a central thread of control, with the event and thread of execution being handed over to the appropriate task. It is now the tasks job to deal with that event and subsequently return the thread of execution back to the executive or dispatcher.
In an event driven system, a task, when allowed to run, must return control when it no longer requires it or when it can not precede further. That is, the task cannot perform an operation which would cause execution to block within that task. If the task was half way through an operation and was waiting on more data, it would need to remember where it was and return. When the data it was waiting on arrived it would then continue from where it had previously stopped. The thread of execution must be given back to the dispatcher in order to allow other tasks to run and deal with their own events. The tasks therefore must cooperate with each other to ensure that all are able to get through their respective jobs.
The main difference with an event driven system is that the central executive or dispatcher must be aware of a number of primary event sources. When the dispatcher goes to retrieve the next job to execute, it must also check the event sources and if an event is waiting, create a job for the delivery of the event to the appropriate task.
In a simulation system, when there are no more jobs to execute the program would terminate. For an event driven system, while ever any of the primary event sources are active and tasks are interested in events from those sources, the process will continue to execute. This means that if there were no jobs to execute in the list of jobs, the process would block and wait for an event to arrive. As the process itself is blocked, those events will of necessity be the result of some stimulus from the operating system. For example, a timer or alarm expiring, a signal being directed at the process, or data being ready for the process to read.
class Job1 : public OTC_JobThe first step which is necessary is to initialise the dispatcher by calling the static member function of OTC_Dispatcher called `initialise()'. This will create the internal job queue on which jobs are kept and set the state of the dispatcher to that of being idle. Note that the dispatcher should only be initialised once `main()' has started executing. The dispatcher should not be initialised as a result of code which is run due initialisation of a static object.
{
public:
void execute();
private:
static int count;
};
int Job1::count = 0;
void Job1::execute()
{
if (++count != 10)
OTC_Dispatcher::schedule(new Job1);
cout << count << endl;
}
main()
{
OTC_Dispatcher::initialise();
OTC_Dispatcher::schedule(new Job1);
OTC_Dispatcher::run();
return 0;
}
The next step is to schedule any jobs which are to be executed by calling the static member function of OTC_Dispatcher called `schedule()' with a pointer to the job object as argument. Jobs should only be scheduled after the dispatcher has been initialised. Any jobs scheduled should be created on the free store using `operator new()'. This is necessary as the dispatcher will take responsibility for ensuring that jobs are destroyed once they have finished executing. How to override this and control when jobs are destroyed will be detailed later.
Once all jobs to be executed have been scheduled, the dispatcher is run by calling the static member function `run()' of the OTC_Dispatcher class. While there are still jobs on the job queue, the dispatcher will take the first job from the queue and call the `execute()' member function. When there are no more jobs to run, the `run()' function will return, allowing the program to terminate.
As the job object is transient, that is, it will be deleted once the `execute()' function returns, it is unlikely that the job object will be the owner of the data being operated upon. Instead, the job object will generally act purely as a means of starting off a series of operations in some object having a lifetime much longer than that of the job object.
If the `execute()' function cannot complete the job and needs to continue with it at a later time, a new job will need to be scheduled with the dispatcher. The new job may need to be passed state information as to where the first job object stopped. An alternative is to reschedule the same job object. In this case though, it is necessary to prevent the dispatcher from destroying the job object while it is still required. This is done by overriding the `destroy()' member function of the OTC_Job class.
class Job2 : public OTC_JobAs is, there is no way to stop a job from executing once it has been scheduled. If this attribute is needed, your job object will need to keep a flag to indicate whether it is to be run. The `execute()' function should check this flag and subsequently run or not run.
{
public:
void execute();
void destroy();
private:
static int count;
};
int Job2::count = 0;
void Job2::execute()
{
if (++count != 10)
OTC_Dispatcher::schedule(this);
cout << count << endl;
}
void Job2::destroy()
{
if (count == 10)
delete this;
}
class Job3 : public OTC_JobThe existence of the `kill()' member function presumes that something has kept a pointer to the job object in order that it can be called. To ensure that a pointer to the job object is not kept after it has been deleted, the `execute()' function would need to do whatever was necessary to ensure that whatever kept the pointer threw it away when the job had completed. It is presumed that if `kill()' was called, that the caller would throw away the pointer as it would know that it would no longer be valid.
{
public:
Job3() : runnable(OTCLIB_TRUE) {}
void kill() { runnable = OTCLIB_FALSE; }
void execute();
void destroy();
private:
static int count;
OTC_Boolean runnable;
};
int Job3::count = 0;
void Job3::execute()
{
if (runnable)
{
if (++count != 10)
OTC_Dispatcher::schedule(this);
cout << count << endl;
}
}
void Job3::destroy()
{
if (!runnable || count == 10)
delete this;
}
Note that the code above will not result in the job object being destroyed when the `kill()' function being executed. Instead, the job object will remain in the job queue until it becomes the first job in the queue. At that point the dispatcher will execute the job by calling the `execute()' function, however nothing would be done and the function return. The job object will subsequently be destroyed when the dispatcher calls the `destroy()' member function.
Leaving the job object in the queue and only destroying it when it becomes the first job, avoids having to traverse a list of jobs to remove it. Leaving cancellation of jobs up to the user for those cases where it is necessary, also simplifies the code needed to implement jobs for the case where cancellation isn't required.
A further case of where `destroy()' could be overloaded, is to allow jobs to exist in extents other than on the free store, or to implement a reference counting mechanism for job objects. Both cases can be useful where a job is used many times and may even appear in the job queue more than once. If though a job object were to appear more than once in the queue, it would not be able to hold any data which needed to be different between each schedule job. You also would not be able to add the ability to cancel individual jobs.
class Job4 : public OTC_Job
{
public:
void execute();
void destroy();
private:
static int count;
};
int Job4::count = 0;
void Job4::execute()
{
if (++count != 10)
OTC_Dispatcher::schedule(this);
cout << count << endl;
}
void Job4::destroy()
{
// Nothing to do.
}
main()
{
static OTC_Job4 job;
OTC_Dispatcher::initialise();
OTC_Dispatcher::schedule(&job);
OTC_Dispatcher::schedule(&job);
OTC_Dispatcher::schedule(&job);
OTC_Dispatcher::run();
return 0;
}
If it is necessary to make the dispatcher return while there are still jobs in the queue to execute, the static member function `stop()' of the OTC_Dispatcher class should be called. This will cause the `run()' member function to return as soon as the job in which `stop()' was called had returned from the `execute()' function.
The `stop()' member function would be used where it was necessary to cleanly exit from a program when the desired result had been achieved, or some error had been encountered which prevented further processing. If the dispatcher is stopped prematurely, the `run()' member function will return a value of `-1'. On normal termination, the `run()' member function would have returned `0'.
Events may be created and sent from anywhere within an application. The recipient of an event is referred to as an agent. To receive events, agents must derive from the class OTC_EVAgent. The OTC_EVAgent class provides a means of identifying each agent and an abstract interface for passing events to the derived class.
A range of predefined events are provided with the library. The simplest of these is the OTCEV_Action class. This event is used as a means of indicating to an agent that its turn to run has come. This event is useful where an operation is to be performed in steps which potentially must interleave with the delivery of other events. For example:
class Agent1 : public OTC_EVAgent
{
public:
Agent1() : count(0) {}
void handle(OTC_Event* e);
private:
u_int count;
};
void Agent1::handle(OTC_Event* e)
{
if (e->type() == OTCEV_Action::typeId())
{
count++;
cout << count << endl;
if (count < 10)
{
e->queue(id());
return;
}
}
e->destroy();
}
main()
{
Agent1 a;
OTC_Dispatcher::initialise();
OTCEV_Action* e = new OTCEV_Action;
e->queue(a.id());
OTC_Dispatcher::run();
return 0;
}
Sending an event to an agent can be carried out in a number of ways. The first is to use the `queue()' member function of the OTC_Event class.
Agent1 a;The `queue()' member function takes as argument the unique ID for the agent to which the event should be delivered. The result of the function is to create a job for the delivery of the event to the agent and add the job to the dispatchers job queue. The event will be delivered to the agent after any jobs already present in the dispatchers job queue have been run.
OTC_Event *e;
e = new OTCEV_Action;
e->queue(a.id());
If it was necessary for the event to be delivered immediately to the agent, bypassing the dispatchers job queue, the `deliver()' member function should be used. The `deliver()' member function takes as argument either the unique ID for an agent, or a pointer to the agent. The latter type of argument is more efficient as it avoids the need to map the unique ID of the agent into a pointer to the agent. If using a pointer to the agent however, you must know that it still exists.
Agent1 a;In all cases, to deliver an event to an agent, the request is made to the event object. The event object will use whatever mechanism is appropriate to ensure it arrives. When the event does arrive, it is passed to the agent through the `handle()' member function. An agent must override the `handle()' member function to interpret the events.
OTC_Event* e;
e = new OTCEV_Action;
e->deliver(&a);
void Agent1::handle(OTC_Event* e)The job of the agent is to ascertain if an event it receives is an event which it was expecting. If the event was unexpected it should be destroyed by calling the `destroy()' member function. It may also be appropriate to print a diagnostic message for the case of an unexpected event.
{
if (e->type() == OTCEV_Action::typeId())
{
count++;
cout << count << endl;
if (count < 10)
{
e->queue(id());
return;
}
}
e->destroy();
}
To determine the type of an event the run time type identification system built into each event type should be used. To get the identifier for the type of an event, the `type()' member function is used. The type identifier should then be compared to that which was expected. The type identifier of the expected event is obtained by calling the static member function `typeId()' of the appropriate event class.
If an event contains data, it will be necessary to perform a downcast from the type OTC_Event to the true type of the event. As downcasting may be necessary, a type derived from OTC_Event should never use virtual inheritance.
When the event is no longer required it should be destroyed by calling the `destroy()' member function. If the event is to be reused, through forwarding it onto a different agent, or arranging for it to be delivered back to the same agent, it would not be destroyed. Only when the event is no longer needed should it be destroyed.
The second member function which needs to be added, overrides the `type()' member function defined in the OTC_Event class. This function should call `typeId()' to return the unique identifier for that type of event.
class Event1 : public OTC_EventTo identify this event from the `handle()' member function of an agent, you would now write:
{
public:
void* type() const;
static void* typeId();
};
void* Event1::type() const
{
return typeId();
}
void* Event1::typeId()
{
static int dummy = 0;
return &dummy;
}
if (e->type() == Event1::typeId())Note that this mechanism does not support having hierarchies of events. Each event class needs to inherit directly from OTC_Event. If you wish to create event hierarchies you will need to use some intermediate class which encapsulates a method for determining if an event is a derived version of some other event.
...
The diagnostic message will contain a brief description giving the event type and the ID of the agent to which it was destined. The description of the event is provided by the `dump()' member function. When defining a new event, you will need to override this member function to display information appropriate to your event. For example:
void OTCEV_IOEvent::dump(ostream& outs) constWhen an event cannot be delivered, the `cancelSource()' member function will also be called. The member function will be passed the ID of the agent to which the event was to be delivered. If necessary, this member function should be overridden to undertake whatever is necessary to ensure that the event source where the event originated, is notified that it shouldn't send further events to that agent.
{
outs << "<OTC> IOEVENT - fd = " << fd()
<< ", events = "<< events();
}
void OTCEV_IOEvent::cancelSource(int theAgentId)
{
OTCEV_IOEvent::unsubscribeAgent(theAgentId);
}
void Agent2::handle(OTC_Event* e)By default. the `clone()' and `destroy()' member functions implement a reference counting mechanism. This ensures that the event will only be deleted when the number of calls to `destroy()' has exceeded the number of calls to `clone()'. The `destroy()' member function needs to be called an additional time to account for the original object.
{
for (int i = 0; i<nagents; i++)
{
OTC_Event* ce = e->clone();
ce->queue(agents[i]);
}
e->destroy();
}
If an event object contains data which is modifiable by the recipient of the event, it will be necessary to override the implementation of the `clone()' function such that it creates and returns a copy of the object. If this is not done, one agent could modify the data associated with an event before other agents had an opportunity to read it.
OTC_Event* Event2::clone() const
{
return new Event2(*this);
}
If the delay in an event being delivered and the inability to prevent it from being delivered once queued is a problem, it may be more appropriate to use the `deliver()' member function. The `deliver()' member function ensures that the event is delivered immediately, avoiding the need to be able to prevent it from being delivered after it is queued.
Whether you can prevent delivery of an event will depend on whether the event is a stand alone object which you are responsible for creating, or whether it originates from an event source. As an example, the OTCEV_Action class provides an interface which negates you having to create an instance of the event in order to send it to an agent.
Agent1 a;The benefit of using the static member function `schedule()' is that creation of the event object is hidden. In addition, the ID returned by the `schedule()' function can be captured and used to later prevent the event from being delivered. It is also possible to prevent all instances of this type of event from being delivered to a particular agent.
OTCEV_Action::schedule(a->id());
int id = OTCEV_Action::schedule(a->id());To ascertain if an event has been delivered, the static member function `active()' can be used.
// cancel single event
OTCEV_Action::cancel(id);
// cancel all events for agent
OTCEV_Action::cancelAgent(a->id());
int id = OTCEV_Action::schedule(a->id());
if (OTCEV_Action::active(id))
...
Extending the functionality of the dispatcher is achieved by replacing the job queue. This is possible as the job queue is not just a list of jobs, but provides an encapsulation of the operations for adding a new job to the queue and retrieving the first job in the queue. The name of the default job queue is the class OTC_JobQueue.
The job queue is extended by overriding in a derived class the `next()' member function. This member function is used to obtain the next job to be executed. The dispatcher is then made to use the derived class instead of OTC_JobQueue. The name of the derived class which is provided and which is aware of the real time aspects of the UNIX operating system is OUX_JobQueue. To have the dispatcher use this class, it is initialised as follows.
main()In the OUX_JobQueue class, when the `next()' member function is called, the various sources of real time events will be checked. If any events are available a job corresponding to the delivery of that event will be returned. If no events are available at that time, the job queue will go into a mode whereby it will return each of the jobs in the job queue held by the OTC_JobQueue base class.
{
OUX_JobQueue* queue;
queue = new OUX_JobQueue;
OTC_Dispatcher::initialise(queue);
...
OTC_Dispatcher::run();
return 0;
}
When the job queue is empty, checking for real time events will recommence. If new jobs were added while in the mode where the job queue was being emptied, they would not be handled at that time, but would be returned the next time the job queue was being emptied. If there were no jobs in the job queue, the `next()' member function would block until a real time event occurs.
Note that the description above has been simplified. In reality, certain real time events will pre-empt the return of a job from the job queue when it is being emptied. Within the real time events, there also exists a priority as to which types of events are dealt with before others. Finally, due to the necessity to deal with real time events before other jobs, jobs in the job queue have been demoted to the point that they are only run when there is nothing else to do. Although, when run, they will all be run before returning to check for all real time events.
To be notified of any of the real time events, you need to subscribe to that type of event through its appropriate class. The event classes corresponding to the supported real time events are OTCEV_Alarm, OTCEV_Timeout, OTCEV_IOEvent and OUXEV_Signal.
class Agent2 : public OTC_AgentWhen an alarm subscription is made, the unique ID for the agent to which the event should be delivered and the time the alarm is to expire, must be supplied. Subscriptions are made by calling the static member function `set()'. The value returned by `set()' is the ID for that alarm. This ID can be used to cancel the alarm by calling the static member function `cancel()'. All alarms destined for a specific agent can be cancelled by calling the static member function `cancelAgent()' with the ID of the agent.
{
public:
Agent2();
~Agent2();
private:
void handle(OTC_Event* e);
int aid;
};
Agent2::Agent2()
{
struct tm t;
t.tm_day = 25;
t.tm_mon = 12;
t.tm_year = 1994;
t.tm_sec = 0;
t.tm_min = 0;
t.tm_hour = 0;
time_t s = localtime(&t);
aid = OTCEV_Alarm::set(id(),s);
}
void Agent2::handle(OTC_Event* e)
{
if (e->type() == OTCEV_Alarm::typeId())
{
OTCEV_Alarm* ae = (OTCEV_Alarm*)e;
if (ae->alarm() == aid)
{
cout << "Merry Christmas" << endl;
aid = 0;
}
}
e->destroy();
}
Agent2::~Agent2()
{
if (aid != 0)
OTCEV_Alarm::cancel(aid);
}
A value of `0' is never returned as an alarm ID and therefore can be used to indicate that no alarm is pending. By calling the static member function `active()' you can determine if an alarm is still to be triggered. The argument to `active()' should be the alarm ID.
Alarms allow you to specify an absolute time, with the unit of time being a second. If you need to specify time periods of less than a second, a timer should be used. Subscription to timers is carried out through the OTCEV_Timeout class. For timers, the time at which event notification should occur is expressed as a relative time, the unit of time being milliseconds.
For example, to request that an event be sent to an agent half a second in the future, you would write:
OTCEV_Timeout::start(a->id(),500);As for alarms, an ID is returned by the static member function `start()'. This ID can be used to cancel the timer before it has expired. All timers associated with an agent may also be cancelled.
OTCEV_IOEvent::subscribe(a->id(),fd,OTCLIB_POLLIN);To unsubscribe from a file descriptor you use the ID of the agent in conjunction with the file descriptor. You may optionally indicate which type of i/o event on the file descriptor you wish to unsubscribe.
OTCEV_IOEvent::subscribe(a->id(),fd,OTCLIB_POLLOUT);
OTCEV_IOEvent::unsubscribe(a->id,fd,OTCLIB_POLLOUT);It is also possible to unsubscribe from a file descriptor using just the file descriptor.
OTCEV_IOEvent::unsubscribeFd(fd);All file descriptors in which an agent is interested may be also be unsubscribed.
OTCEV_IOEvent::unsubscribeAgent(a->id());When an event for a file descriptor is delivered, the file descriptor and the type of i/o event will be encapsulated into the event. If more than one i/o event for a file descriptor occurs, only one event object will be delivered. You will need to decode the variable giving the type of i/o event to determine which occurred.
void Agent3::handle(OTC_Event* e)If dealing with multiple i/o events, a check may need to be done for succeeding i/o event within that object to determine if processing should occur. For example, one i/o event may result in the file descriptor being closed, obviating the need to process the other i/o events within that event object.
{
if (e->type() == OTCEV_IOEvent::typeId())
{
OTCEV_IOEvent* ioe = (OTCEV_IOEvent*)e;
cout << ioe->fd() << endl;
if (ioe->events() & OTCLIB_POLLPRI)
...
if (ioe->events() & OTCLIB_POLLIN)
...
}
e->destroy();
}
int cont = 1;
if (ioe->events() & OTCLIB_POLLPRI)
{
...
if (...)
{
close(ioe->fd());
cont = 0;
}
}
if (cont && (ioe->events() & OTCLIB_POLLIN))
...
Agent5::Agent5()
{
OUXEV_Signal::subscribe(id(),SIGNINT);
OUXEV_Signal::subscribe(id(),SIGUSR1);
}
void Agent5::handle(OTC_Event* e)
{
if (e->type() == OUXEV_Signal::typeId())
{
OUXEV_Signal* se = (OUXEV_Signal*)e;
if (se->signal() == SIGINT)
OTC_Dispatcher::stop();
else if (se->signal() == SIGUSR1)
...
}
e->destroy();
}
Agent5::~Agent5()
{
OUXEV_Signal::unsubscribeAgent(id());
}
In order to use the dispatcher with the TK GUI library, patches are provided for the TK dispatcher so that the necessary information can be obtained. Provided the patches are applied and the OTKLIB library installed, a version of `wish' can be built which uses the OSE dispatcher. To do this, at the start of the `main()' for `wish' add:
OTK_JobQueue* queue;Instead of invoking `TkMainLoop()' the following call should be made.
queue = new OTK_JobQueue;
OTC_Dispatcher::initialise(queue);
OTC_Dispatcher::run();Full integration with TK has been achieved. You are not limited as to the features of TK which you can use.
For the X Intrinsic library, it is not realistic to expect that the library be patched. For this reason full integration with Xt is not possible. As many GUI packages such as Motif rely on the Xt dispatcher those libraries are also affected.
What is possible is to create an agent representing the connection to the X display. The agent will subscribe to the file descriptor used for the X display and whenever there is data waiting on the file descriptor it will call the appropriate Xt routines to process the data and flush out any message queues. The code for this agent is as follows.
class XtAgent : public OTC_AgentAs full integration cannot be accomplished, any interest in timers and file descriptors registered through the routines `XtAddInput()' and `XtAddTimeout()' are not handled correctly. Although the Xt events for these event sources will get delivered, they will not always be delivered at the time they occur. The reason for this is that the events will only be processed and delivered when the Xt routines are called to handle data ready for reading from the X display.
{
public:
XtAgent(
XtAppContext theAppContext,
Display* theDisplay
);
~XtAgent();
protected:
void handle(OTC_Event* theEvent);
private:
XtAppContext myXtAppContext;
Display* myDisplay;
};
XtAgent::XtAgent(
XtAppContext theAppContext,
Display* theDisplay
)
: myXtAppContext(theAppContext),
myDisplay(theDisplay)
{
OTCEV_IOEvent::subscribe(id(),
myDisplay->fd,OTCLIB_POLLIN);
}
XtAgent::~XtAgent()
{
OTCEV_IOEvent::unsubscribe(id(),myDisplay->fd);
}
void XtAgent::handle(OTC_Event* theEvent)
{
if (theEvent->type() == OTCEV_IOEvent::typeId())
{
OTCEV_IOEvent* theIOEvent = (OTCEV_IOEvent*)theEvent;
if (theIOEvent->fd() == myDisplay->fd
&& theIOEvent->events() == OTCLIB_POLLIN)
{
while (XtAppPending(myXtAppContext) != 0)
XtAppProcessEvent(myXtAppContext, XtIMAll);
XFlush(myDisplay);
}
}
theEvent->destroy();
}
The only known consequence of this is that in Motif, holding down a button for an extended time on a scroll down arrow, will not result in continuous scrolling. Moving the mouse around while the button is held down will result in scrolling occurring as the mouse motion will result in X events being sent from the X display to the application, triggering the XtAgent class and the processing of all Xt events which are pending.
The benefit of the open style of interfaces is that customised interfaces can easily be built on top. This has allowed in the past, software from two different groups which each provided their own object oriented framework for handling real time events to be relatively easily integrated. One groups was using a modified version of the InterViews dispatcher, the other group was using a home grown dispatcher.
The solution was that the internal implementations of each dispatcher were removed and the interfaces bolted on top of the dispatcher provided in the library. This allowed each group to continue to use their own interfaces for subscribing and responding to real time events.
In the case of the InterViews style of interfaces, the user only had to know about two classes. The first class was IOHandler. This class derived from OTC_EVAgent and mapped events received by the `handle()' member function to individual virtual function calls. An implementation similar to that which was used is given below.
class IOHandler : private OTC_AgentThe second class was the Dispatcher class. This class was the interface for subscribing to all the different types of real time events. It also provided functions for starting the dispatcher.
{
friend class Dispatcher;
public:
virtual int inputReady(int fd);
virtual int outputReady(int fd);
virtual int exceptionRaised(int fd);
virtual int errorRaised(int fd);
virtual int signalRaised(int sig);
virtual void timerExpired(int id);
virtual void alarmTriggered(int id);
protected:
IOHandler() {}
void handle(OTC_Event* theEvent);
};
int IOHandler::inputReady(int fd) { return -1; }
int IOHandler::outputReady(int fd) { return -1; }
int IOHandler::exceptionRaised(int fd) { return -1; }
int IOHandler::errorRaised(int fd) { return -1; }
int IOHandler::signalRaised(int sig) { return -1; }
void IOHandler::timerExpired(int id) { }
void IOHandler::alarmTriggered(int id) { }
void IOHandler::handle(OTC_Event* theEvent)
{
if (theEvent->type() == OTCEV_IOEvent::typeId())
{
OTCEV_IOEvent* theIOEvent;
theIOEvent = (OTCEV_IOEvent*)theEvent;
int fd = theIOEvent->fd();
int events = theIOEvent->events();
int result = 0;
if (events & OTCLIB_POLLIN)
result = inputReady(fd);
if (result != -1 && events & OTCLIB_POLLOUT)
result = outputReady(fd);
if (result != -1 && events & OTCLIB_POLLPRI)
result = exceptionRaised(fd);
if (result != -1 && events &
(OTCLIB_POLLERR | OTCLIB_POLLHUP | OTCLIB_POLLNVAL))
result = errorRaised(fd);
if (result == -1)
OTCEV_IOEvent::unsubscribe(id(),theIOEvent->fd());
}
else if (theEvent->type() == OTCEV_Timeout::typeId())
{
OTCEV_Timeout* theTimeout;
theTimeout = (OTCEV_Timeout*)theEvent;
timerExpired(theTimeout->timer());
}
else if (theEvent->type() == OTCEV_Alarm::typeId())
{
OTCEV_Alarm* theAlarm;
theAlarm = (OTCEV_Alarm*)theEvent;
alarmTriggered(theAlarm->alarm());
}
else if (theEvent->type() == OUXEV_Signal::typeId())
{
OUXEV_Signal* theSignal;
theSignal = (OUXEV_Signal*)theEvent;
int result;
result = signalRaised(theSignal->signal());
if (result == -1)
OUXEV_Signal::unsubscribe(id(),theSignal->signal());
}
theEvent->destroy();
}
class DispatcherOne benefit of this interface was that because the static member function `instance()' had to be used to get at the dispatcher in order to perform subscriptions, the dispatcher could be created and initialised the first time it was used. This avoided the restriction of the lower level interface that the dispatcher couldn't be initialised and certain subscriptions made until after `main()' had started executing.
{
public:
void link(
int theFd,
the theEvent,
IOHandler* theHandler
);
void unlink(int theFd);
void unlink(IOHandler* theHandler);
void subscribe(int theSig, IOHandler* theHandler);
void unsubscribe(int theSig, IOHandler* theHandler);
int startTimer(
long theSec,
long theUSec,
IOHandler* theHandler
);
void stopTimer(int id);
int setAlarm(long theTime, IOHandler* theHandler);
void cancelAlarm(int id);
void dispatch();
static Dispatcher& instance();
private:
static Dispatcher* _instance;
};
Dispatcher* Dispatcher::_instance = 0;
void Dispatcher::link(
int theFd,
int theEvent,
IOHandler* theHandler
)
{
OTCEV_IOEvent::subscribe(theHandler->id(),theFd,theEvent);
}
void Dispatcher::unlink(int theFd)
{
OTCEV_IOEvent::unsubscribeFd(theFd);
}
void Dispatcher::unlink(EX_IOHandler* theHandler)
{
OTCEV_IOEvent::unsubscribeAgent(theHandler->id());
}
void Dispatcher::subscribe(
int theSig,
IOHandler* theHandler
)
{
OUXEV_Signal::subscribe(theHandler->id(),theSig);
}
void Dispatcher::unsubscribe(
int theSig,
IOHandler* theHandler
)
{
OUXEV_Signal::unsubscribe(theHandler->id(),theSig);
}
int Dispatcher::startTimer(
long theSec,
long theUSec,
IOHandler* theHandler
)
{
long period = theSec*1000 + theUSec/1000;
return OTCEV_Timeout::start(theHandler->id(),period);
}
void Dispatcher::stopTimer(int id)
{
OTCEV_Timeout::cancel(id);
} int Dispatcher::setAlarm(
long theTime,
IOHandler* theHandler
)
{
return OTCEV_Alarm::set(theHandler->id(),theTime);
}
void Dispatcher::cancelAlarm(int id)
{
OTCEV_Alarm::cancel(id);
}
void Dispatcher::dispatch()
{
OTC_Dispatcher::run();
}
Dispatcher& Dispatcher::instance()
{
if (_instance == 0)
{
OTC_JobQueue* theJobQueue;
theJobQueue = new OUX_JobQueue;
OTC_Dispatcher::initialise(theJobQueue);
_instance = new EX_Dispatcher;
}
return *_instance;
}