There are three main methods that the kernel calls in the application code. The first is initialize. This method takes no parameters, and is called exactly once in each simulation object at the beginning of the simulation. Since WARPED requires that every object in the system is event driven, a good use of the initialize function is to have some objects send themselves events to ``prime'' the simulation. The PingObject with id == 0 does this using the following code:
void
PingObject::initialize(){
state.current->initialize();
if( id == 0 ){
// we need to give ourselves the first ball to start...
BasicEvent *firstBall = new BasicEvent;
firstBall->dest = id;
firstBall->recvTime = 0;
firstBall->size = sizeof( *firstBall );
sendEvent( firstBall );
}
}
Note the call to state.current->initialize(). As previously discussed, default constructors to initialize the states would be inefficient due to the fact that states are normally copied over once they have been allocated. This being the case, an explicit call to the user-defined initialize function is necessary once at the beginning of the simulation.
The rest of the method arbitrarily chooses object with id == 0 to start the simulation by sending an event to itself. The method sendEvent will be discussed in detail shortly.
The next application method that the kernel will call is executeProcess. This method defines the simulation object's behavior each time it gets scheduled for execution. If an object has no event to process, it is considered idle and won't get scheduled. Furthermore, once executeProcess has been called, control has passed completely from the kernel to the application. Until control is returned to the kernel, no other simulation objects on the same logical process can be scheduled.
A ``typical'' simulation will perform the following activities in a single simulation cycle:
The function executeProcess from the PingObject class follows:
void
PingObject::executeProcess(){
//we got an event
//let's get the event
BasicEvent *msgGot = getEvent();
if(msgGot != NULL)
{
state.current->numBallsRecvd-- ;
if(id != 0 || (id == 0 && state.current->numBallsRecvd != 0))
{
//we want to send another event
BasicEvent *newMsg = (BasicEvent*) new char[sizeof(BasicEvent)] ;
new (newMsg) BasicEvent();
newMsg->dest = dest;
newMsg->recvTime = state.current->lVT + 1 ;
newMsg->size = sizeof(BasicEvent);
state.current->numBallsSent++;
sendEvent(newMsg);
}
}
}
The last method defined in the application that is called by the kernel is finalize(). This optional method is called once at the end of a simulation in each object. This call is intended to allow the object to collect statistics or do any required housekeeping. The PingObject simply prints a summary of its final state information:
void
PingObject::finalize(){
cout << name << " sent " << state.current->numSent << " balls." << endl;
cout << name << " received " << state.current->numReceived <<
" balls." << endl;
}