For example, an airport designer might know the rates at which airplanes are coming in from other airports and how long it takes for a single plane to land. The designer would like to estimate how many runways are needed to handle the load and how long, on average, the airplanes would have to circle before they could land. Because planes don't come in from different airports at perfectly spaced intervals, it would be hard to figure out the answers to these questions by hand. However, a simple program to simulate the airport traffic can give good estimates to the designer.
The variation in traffic from the other airports can be modeled by generating arrival times at random from some statistical distribution. Much research has been done on what this distribution should be. The computer simulation just keeps track of the interarrivals as they are randomly picked and computes the estimates needed by the designer.
In this case study, we look at the design of a network router. Network routers are the connection points between different links in a network. Fig. 1 shows a diagram of a simple network router. The router takes incoming messages from other routers and computers on a network (the incoming lines) and copies them to another destination (the outgoing line).
A network router has a set of input lines (feeder airports) that
carry
messages (airplanes) to the node. The router forwards the messages to
the outgoing lines (runways). It takes a certain amount of time to send
the message
on the outgoing line (land the plane). During that time the outgoing
line
(runway) cannot be used for anything else. The designer would like to
know,
given the expected input traffic, how many output lines (runways) are
needed.
If the router does not allow messages to wait (planes to circle) until
an
outgoing line becomes available, the designer would also like to know
how
many messages are lost (Planes are usually sent back to originating
airport
instead of being thrown away.)
Exercise 1: Find another real-world problem that is in the same form as the network router problem and the airport runway simulation. Fill in the equivalents for the following.
| Node: | |
| Message: | |
| Incoming Line: | |
| Outgoing Line: | |
| Waiting: | |
A second, more-common strategy is called event-driven simulation. Each thing that happens is represented by an event. Examples of events are the arrival of a message or the sending of a message in the network simulation. In the airport simulation, events may include taking off, circling the airport, landing and taxiing to the gate. The components of the model generate events. The simulation itself keeps track of the events and sends them to the components for processing in the order that they occurred. Time is updated to the time of the next event at each step in the simulation.
The idea of event-driven operation may seem strange at first. However, you have experienced this mode of operation in using interactive computer applications such as word processors. When you open such an application, the program does an initial setup and then waits for an event (e.g., a keystroke or a mouse-click from you indicating what to do). The program processes the event and then resumes waiting for you to do something else. In contrast, most of the programs that you write for your courses are execution-driven. They begin execution at a well-defined point (the main method) and continue executing a well-specified sequence of commands until completion.A computer simulation of the airport or network router needs appropriate bookkeeping to keep track of everything that is going on. Our event-driven simulation uses object-oriented programming techniques that make it relatively easy to keep track of time, the components and the events (interactions among the components). A SimulationModel object will have a field keeping track of the current time. Each event will be represented by an Event object. Components will be represented as EventHandler objects, which may generate events (such as message arrivals and airplane arrivals) and handle events (that are generated by other event handlers, such as to route a message or to land an airplane on a specific runway). Typically, the handling of an event results in generating a new event. To manage the generation and the handling of these events in the order in which they suppose to happen in time, the simulation model uses efficient data structures to manage the set of events and the set of event handlers.
Exercise 2: For the example that you gave in Exercise 1, list
possible events.
public class Event implements Comparable {
public static final int STOP = 0;
public static final int MSG_ARRIVAL = 1; // message arrived at router
public static final int MSG_ROUTE = 2; // message assigned to outgoing line
public static final int MSG_SENT = 3; // message sent by outgoing line
private int who; // Who did it
private int what; // What they did
private double when; // When they did it
private int whom; // Whom is it for
public Event(int who, int what, double when, int whom) {
this.who = who;
this.what = what;
this.when = when;
this.whom = whom;
}
public int getWho( ) { return who; }
public int getWhat( ) { return what; }
public double getWhen( ) { return when; }
public int getWhom() { return whom; }
public int compareTo(Object obj) {
Event e = (Event) obj;
double timeDifference = when - e.when;
if (timeDifference < 0)
return -1;
else if (timeDifference > 0)
return 1;
// if equal times, make STOP last
if (what == STOP && e.what != STOP)
return 1;
else if (what != STOP && e.what == STOP)
return -1;
return 0;
}
public String toString() {
return getClass().getName() + "[Who:" + who + ", What:" + what
+ ", When:" + when + ", Whom:" + whom + "]";
}
}
Exercise 4: What are the events in the airport simulation?
In the simulation we will want to process events in the order that they occur. The priority queue is a natural data structure for storing events in a specified order as they come in. We can use the removeMin method of PriorityQueueADT to remove the event from the priority queue with the smallest when, that is, the event that happens next.
public interface PriorityQueueADT <T extends Comparable> {
public void add(T element); // inserts element in priority queue
public T removeMin( ); // removes and returns the smallest item
public T findMin( ); // returns smallest item
public void makeEmpty( ); // removes all items
public boolean isEmpty( ); // returns true if empty; else false
public int size( ); // returns the size of the queue
}
int numberEvents = 100;
double maxTime = 1000;
PriorityQueueADT<Event> p = new LinkedPriorityQueue();
Random rand = new Random();
// Generate the events and put them on the queue
for (int i = 1; i <= numberEvents; i++) {
Event e = new Event(i, Event.MSG_ARRIVAL, rand.nextDouble() * maxTime, i);
p.add(e);
}
double lastArrival = 0.0;
while (!p.isEmpty()) {
Event e = p.removeMin();
System.out.println("Removing " + e);
lastArrival = e.getWhen();
}
System.out.println("The average interarrival: " +
(lastArrival/numberEvents));
The implementation uses the fact that the very last arrival is equal
to
the sum of the interarrival times.
We expect the average to be about 1000/100 = 10.
Exercise 6: Complete Parts I and II of Recitation Laboratory
6.
Exercise 7: What happens when you create an event whose
"what"
does not
correspond to one of the predefined values STOP, MSG_ARRIVAL,
MSG_ROUTE,
or
MSG_SENT?
Ans: Nothing special. The constructor does not check to make
sure that
the event type is valid.
Exercise 8: What data field(s) in the Event class
determine(s) the order that the events are removed from the priority
queue?
Ans: The when field.
Exercise 9: Why does the Event class need to
implement
the Comparable interface?
Ans: The priority queue must have a way of ordering the events.
The Comparable
interface method compareTo permits an ordering.
Exercise 10: Suppose the incoming lines of Fig. 1 generate messages at the following times:
| Line # | Arrival Time | Interarrival Times | Average Interarrival Time |
|
|
10, 45, 64 | 10, 35, 19 | (10+35+19)/3 = 64/3 = 21.33 |
|
|
10, 33, 50, 69 | 10, 23, 17, 19 | (10+23+17+19)/4 = 69/4 = 17.25 |
The network router sees the messages from the two incoming lines interleaved as shown in Fig. 4.
When multiple incoming lines (sources of messages) generate messages for the network router, the router sees the messages interleaved. In this particular run, the average time between messages for the individual lines was 20, so when two lines contribute messages, one would expect a time between messages at the router to be around 20/2 = 10 if the simulation were run for a long time.
| Message Arrival Times: | 10, 10, 33, 45, 50, 64, 69 |
| Time Between Messages: | 10, 0, 23, 12, 5, 14, 5 |
| Average time between messages: | (10+0+23+12+15+14+5)/7 = 69/7 = 9.9 |
In the physical world, incoming lines carry messages to the router, which then copies the messages to the correct output lines based on the message destinations. In the simulation, the IncomingLine objects produce MSG_ARRIVAL events that represent messages in the physical world. In our implementation, the IncomingLine objects generate event objects at time intervals at random with an average time between messages of averageInterarrival.
Fig. 5 shows an implemenation of the IncomingLine. This class keeps an internal clock variable that tracks its time. Each time the getNextArrival method is called, it first picks a value at random from the interval [0, 2*averageInterarrival). Then, the method updates the value of clock by that amount andpublic class IncomingLine implements EventHandler {
private double interarrival;
private int ID;
private int routerID;
private int totalMessages;
private Random rand;
private double clock;
public IncomingLine(int ID, double interarrival, int routerID) {
this.interarrival = interarrival;
this.ID = ID;
this.routerID = routerID;
totalMessages = 0;
rand = new Random(ID + 1); // each Random has different seed
clock = 0;
}
public int getID( ) { return ID; }
public double getClock( ) { return clock; }
public int getTotalMessages( ) { return totalMessages; }
// return the next arrival event
public Event getNextArrival() {
clock += 2 * interarrival * rand.nextDouble();
totalMessages++;
return new Event(ID, Event.MSG_ARRIVAL, clock, routerID);
}
// return the first arrival event
public Event initialEvent() {
return getNextArrival();
}
// return the next arrival event if this is the previous arrival
public Event processEvent(Event event) {
if (ID == event.getWho() &&
Event.MSG_ARRIVAL == event.getWhat())
return getNextArrival();
return null;
}
public String toString() {
return "Incoming: ID=" + ID +
", average interarrival time=" + interarrival +
", current time=" + clock +
", messages generated=" + totalMessages;
}
}
IncomingLine line = new IncomingLine(1, 30, 2);Exercise 13: Complete Part III of Recitation Laboratory 6.
int numberEvents = 100;
for (int i = 0; i < numberEvents; i++)
System.out.println("Event " + i + " is " + line.getNextArrival());
Exercise 14: How is the average interarrival time of an IncomingLine
related to the averageInterarrival parameter in the
constructor?
Ans: They are the same.
Exercise 15: Consider two IncomingLine objects, one
with an
average interarrival time of 10 and the other with an average
interarrival time of
30. Estimate the expected time between interleaved messages if the
simulation is run until an event time exceeds 1200.
Ans: In 1200 time units, the first IncomingLine is
expected to
generage about 120 events, while the second will generate about 40
events. The average time between events can be estimated as 1200/160 =
7.5
Exercise 16: Using Random to generate the
interarrival times
results in a uniform distribution of message arrivials, i.e.,
all message
arrival intervals are equally likely. Do you think this is a good
representation
of message arrivals in the real world?
Ans: No. For a given average arrival interval, messages are
likely
to arrive more closely spaced than the average. A few messages will be
delayed
for very long times --- much longer than twice the average. Research
has shown that the Poisson distribution gives a good statistical
description of arrival times for many real-world problems.
public class SingleIncoming {
private double currentTime;
private PriorityQueueADT<Event> eventSet;
private int numberMessages;
private int numberEvents;
private String title;
private boolean started;
private boolean stopped;
private IncomingLine inLine;
public SingleIncoming(String title, double arrival) {
currentTime = 0.0;
eventSet = new LinkedPriorityQueue<Event>();
numberMessages = 0;
numberEvents = 0;
this.title = title;
started = false;
stopped = false;
inLine = new IncomingLine(1, arrival, 2);
}
public void addEvent(Event e) { eventSet.add(e); }
public int getMessages( ) { return numberMessages; }
public int getEvents( ) { return numberEvents; }
public double getTime( ) { return currentTime; }
public String getTitle( ) { return title; }
public void setStopped( ) { stopped = true; }
public void initializeEvents() {
addEvent(inLine.getNextArrival());
}
public void processEvent(Event e) {
switch (e.getWhat()) {
case Event.MSG_ARRIVAL:
addEvent(inLine.getNextArrival());
numberMessages++;
break;
case Event.STOP:
setStopped();
break;
default:
System.err.println(e + " {unrecognized event}");
break;
}
}
public void runSimulation(double stoppingTime) {
if (!started) {
initializeEvents();
started = true;
}
addEvent(new Event(0, Event.STOP, stoppingTime, 0));
stopped = false;
while (!stopped && !eventSet.isEmpty()) {
Event e = eventSet.removeMin();
numberEvents++;
currentTime = e.getWhen();
processEvent(e);
}
}
}
SingleIncoming sim = new SingleIncoming("One incoming", 30);
sim.runSimulation(100);
System.out.println("Time = " + sim.getTime() +
", Number messages:" + sim.getMessages() +
", Number events:" + sim.getEvents());
sim.runSimulation(300);
System.out.println("Time = " + sim.getTime() +
", Number messages:" + sim.getMessages() +
", Number events:" + sim.getEvents());
The simulation will be performed using a class (SimulationModel) that includes methods for adding EventHandler objects and simulating them. The key methods of SimulationModel are shown in Fig. 10.
SimulationModel(String title); // indicates the particular pro oblem
public final void addHandler(EventHandler h); // add h to handlerSet
public final void addEvent(Event e); // add e to eventSet
public void initializeEvents( ); // generate starting events
public void processEvent(Event e) // process an event
public final void runSimulation(double stoppingTime);
public final void setStopped( ); // sets a flag to stop simulation
public final void setDebug(boolean debug); // sets a flag to print events
The final methods are complete and cannot be changed by
any subclass of SimulationModel. We will write
subclasses that extend SimulationModel for each
specific simulation. The subclass can add to or override the
constructor and the initializeEvents
and processEvent methods. In this way, we have separated what
is common to all simulations in the superclass and the specifics in
the subclass. Fig. 11 shows the actual code for the SimulationModel class.
public class SimulationModel {
private ArraySet<EventHandler> handlerSet;
private PriorityQueueADT<Event> eventSet;
private double currentTime;
private int numberEvents;
private boolean started;
private boolean stopped;
private String title;
private boolean debug;
public SimulationModel(String title) {
currentTime = 0.0;
eventSet = new LinkedPriorityQueue<Event> ();
handlerSet = new ArraySet<EventHandler> ();
numberEvents = 0;
this.title = title;
started = false;
stopped = false;
}
public final int getEvents( ) { return numberEvents; }
public final double getTime( ) { return currentTime; }
public final String getTitle( ) { return title; }
public final void setStopped( ) { stopped = true; }
public final void setDebug(boolean debug) { this.debug = debug; }
public final void addEvent(Event e) {
if (e != null) {
if (debug) System.out.println("Adding " + e);
eventSet.add(e);
}
}
public final void addHandler(EventHandler h) {
handlerSet.add(h);
}
public void initializeEvents() {
Iterator<EventHandler> scan = handlerSet.iterator();
while (scan.hasNext())
addEvent(scan.next().initialEvent());
}
public void processEvent(Event e) {
if (debug) System.out.println("Processing " + e);
if (e.getWhat() == Event.STOP) {
setStopped();
} else {
Iterator<EventHandler> scan = handlerSet.iterator();
while (scan.hasNext())
addEvent(scan.next().processEvent(e));
}
}
final public void runSimulation(double stoppingTime) {
if (!started) {
initializeEvents();
started = true;
}
addEvent(new Event(0, Event.STOP, stoppingTime, 0));
stopped = false;
while (!stopped && !eventSet.isEmpty()) {
Event e = eventSet.removeMin();
numberEvents++;
currentTime = e.getWhen();
processEvent(e);
}
}
public String toString() {
String result = title + ": Time = " + currentTime
+ ", Events = " + numberEvents;
Iterator<EventHandler> scan = handlerSet.iterator();
while (scan.hasNext())
result += "\n" + scan.next().toString();
return result;
}
}
public class IncomingOnlyModel extends SimulationModel {
private int numberMessages;
public IncomingOnlyModel(String title, double arrival) {
super(title); // do all of the general initialization first
addHandler(new IncomingLine(1, arrival, 2));
numberMessages = 0;
}
public void processEvent(Event e) {
super.processEvent(e);
switch (e.getWhat()) {
case Event.MSG_ARRIVAL:
numberMessages++;
break;
case Event.STOP:
break;
default:
System.err.println(e + " {unrecognized event}");
break;
}
}
public String toString() {
return super.toString() + "\n" + "numberMessages=" + numberMessages;
}
}
An outgoing line will need to be represented and will be responsible
for the following information:
public class OutgoingLine implements EventHandler {
private int ID;
private int messagesSent;
private int messagesLost;
private double sendTime; // time to send a message
private double whenDone;
public OutgoingLine(int ID, double sendTime) {
this.ID = ID;
messagesSent = 0;
messagesLost = 0;
this.sendTime = sendTime;
whenDone = 0.0;
}
public int getID( ) { return ID; }
public int getMessagesSent( ) { return messagesSent; }
public int getMessagesLost( ) { return messagesLost; }
public double getSendTime( ) { return sendTime; }
public double getWhenDone( ) { return whenDone; }
public double getUtilization() {
return (messagesSent == 0) ? 0.0 : messagesSent * sendTime / whenDone;
}
public Event initialEvent() {
return null; // This has no initial event
}
// If a message is routed to this line, send it or drop it
public Event processEvent(Event event) {
if (ID == event.getWhom() &&
Event.MSG_ROUTE == event.getWhat()) {
if (event.getWhen() >= whenDone) {
messagesSent++;
whenDone = event.getWhen() + sendTime;
return new Event(ID, Event.MSG_SENT, whenDone, event.getWho());
} else {
messagesLost++;
}
}
return null;
}
public String toString() {
return "Outgoing: ID=" + ID + ", line speed=" + sendTime +
", msgs sent=" + messagesSent + ", msgs lost=" + messagesLost +
", utilization=" + getUtilization();
}
}
The types of events that are needed for this simulation are: MSG_ARRIVAL, MSG_ROUTE, and MSG_SENT. The incoming line will produce MSG_ARRIVAL events for the router. The router will respond by producing MSG_ROUTE events for the outgoing line. The outgoing line will respond by producing MSG_SENT events if the messages are not dropped.
Fig. 14 shows the implementation of the unbuffered router. The constructor needs to have the ID of the outgoing line so that events can be created for the outgoing line. The processEvent method recognizes arrival events for the router and routes them to the outgoing line.
public
class UnbufferedRouter
implements EventHandler {
private int ID;
private int outgoingID;
private int messagesRouted;
public
UnbufferedRouter(int ID, int outgoingID) {
this.ID = ID;
this.outgoingID = outgoingID;
messagesRouted
= 0;
}
public int getID( ) {
return ID; }
public int
getMessagesRouted( ) {
return messagesRouted; }
public Event initialEvent( ) {
return null;
// This has no initial event
}
// If a message arrived
for the router, route it
public Event
processEvent(Event event) {
if (ID ==
event.getWhom() &&
Event.MSG_ARRIVAL == event.getWhat()) {
messagesRouted++;
return new Event(ID, Event.MSG_ROUTE, event.getWhen(), outgoingID);
}
return null;
}
public String toString( ) {
return
"Router: ID=" + ID + ",
msgs routed=" + messagesRouted;
}
}
Fig. 15 goes through a trace of a simple example of the unbuffered
simulation. The IDs of the incoming line, router, and outgoing
line are
respectively 1, 2, and 3. The simulation is set to stop at time 25.
The send time for the
outgoing line is 10. The incoming line generates
messages at
times
8, 15 and 19, and 31.
| who | what | when | whom | |
| 1 |
MSG_ARRIVAL | 8 | 2 |
|
| 0 |
STOP | 25 | 0 |
II. while the simulation is not stopped and the priority queue is not empty
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_ARRIVAL |
|
|
|
|
| who | what | when | whom |
| 2 |
MSG_ROUTE |
8 |
3 |
| 1 |
MSG_ARRIVAL | 15 | 2 |
| 0 | STOP | 25 | 0 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_ROUTE |
|
|
|
|
| who | what | when | whom |
| 1 |
MSG_ARRIVAL | 15 | 2 |
| 3 |
MSG_SENT |
18 |
2 |
| 0 | STOP | 25 | 0 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_ARRIVAL |
|
|
|
|
| who | what | when | whom |
| 2 |
MSG_ROUTE | 15 | 3 |
| 3 |
MSG_SENT |
18 |
2 |
| 1 |
MSG_ARRIVAL |
19 |
2 |
| 0 | STOP | 25 | 0 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_ROUTE |
|
|
|
|
| who | what | when | whom |
| 3 |
MSG_SENT | 18 | 2 |
| 1 |
MSG_ARRIVAL | 19 | 2 |
| 0 |
STOP | 25 | 0 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_SENT |
|
|
|
|
| who | what | when | whom |
| 1 |
MSG_ARRIVAL | 19 | 2 |
| 0 |
STOP | 25 | 0 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_ARRIVAL |
|
|
|
|
| who | what | when | whom |
| 2 |
MSG_ROUTE |
19 |
3 |
| 0 |
STOP | 25 | 0 |
| 1 |
MSG_ARRIVAL | 31 | 2 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_ROUTE |
|
|
|
|
EventQueue:
| who | what | when | whom |
| 0 |
STOP | 25 | 0 |
| 3 |
MSG_SENT | 29 | 2 |
| 1 |
MSG_ARRIVAL | 31 | 2 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| STOP |
|
|
|
|
At this point the simulation breaks out of its loop. If we call toString, we will have:
UnbufferedNodeModel: Time = 25.0, Events = 8There is a discrepency between the numbers because the incoming line and outgoing line have generated events past time 25.
Incoming: ID=1, average interarrival time=10.0, current time=31.0, messages generated=4
Router: ID=2, msgs routed=3
Outgoing: ID=3, line speed=1.0, msgs sent=2, msgs lost=1, utilization=0.68965517241379315
Received Messages=3, Routed Messages=3, Sent Messages=1
___|______|______|_____|______|_____|______|_____|______|______|_____|__
0 5 10 15 20 25 30 35 40 45 50
Fig. 16 shows a complete implementation of the UnbufferedNodeModel. It has an IncomingLine, an UnbufferedRouter and an OutgoingLine.
public class UnbufferedNodeModel extends SimulationModel {
private int receivedMessages;
private int routedMessages;
private int sentMessages;
public UnbufferedNodeModel(double arrival, double sendtime) {
super("UnbufferedNodeModel");
addHandler(new IncomingLine(1, arrival, 2));
addHandler(new UnbufferedRouter(2,3));
addHandler(new OutgoingLine(3, sendtime));
receivedMessages = 0;
routedMessages = 0;
sentMessages = 0;
}
public int getReceivedMessages( ) { return receivedMessages; }
public int getSentMessages( ) { return sentMessages; }
public int getRoutedMessages( ) { return routedMessages; }
public void processEvent(Event e) {
super.processEvent(e);
switch (e.getWhat()) {
case Event.MSG_ARRIVAL:
receivedMessages++;
break;
case Event.MSG_ROUTE:
routedMessages++;
break;
case Event.MSG_SENT:
sentMessages++;
break;
case Event.STOP:
break;
default:
System.err.println(e + " {unrecognized event}");
break;
}
}
public String toString() {
return super.toString()
+ "\nReceived Messages=" + getReceivedMessages()
+ ", Routed Messages=" + getRoutedMessages()
+ ", Sent Messages=" + getSentMessages();
}
}
The following changes are needed to convert the UnbufferedRouter class to a BufferedRouter class:
Fig. 16 goes through a trace of a simple example of the buffered
simulation using the same message arrivals as Fig. 15. The IDs of
the incoming line, router, and outgoing line are
respectively 1, 2, and 3. The simulation is set to stop at time 25.
The send time for the
outgoing line is 10. The incoming line generates
messages at
times
8, 15 and 19, and 31. The
trace will be the same until the second message arrives at time 15.
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_ARRIVAL |
|
|
|
|
| who | what | when | whom |
| 3 |
MSG_SENT | 18 | 2 |
| 1 |
MSG_ARRIVAL | 19 | 2 |
| 0 |
STOP | 25 | 0 |
| who | what | when | whom |
| 1 |
MSG_ARRIVAL | 15 | 2 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_SENT |
|
|
|
|
| who | what | when | whom |
| 2 |
MSG_ROUTE |
18 |
3 |
| 1 |
MSG_ARRIVAL | 19 | 2 |
| 0 |
STOP | 25 | 0 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_ROUTE |
|
|
|
|
| who | what | when | whom |
| 1 |
MSG_ARRIVAL | 19 | 2 |
| 0 |
STOP | 25 | 0 |
| 3 |
MSG_SENT | 28 | 2 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| MSG_ARRIVAL |
|
|
|
|
| who | what | when | whom |
| 0 | STOP | 25 | 0 |
| 3 |
MSG_SENT | 28 | 2 |
| 1 |
MSG_ARRIVAL | 31 | 2 |
| who | what | when | whom |
| 1 |
MSG_ARRIVAL | 19 | 2 |
| e | currentTime | numberEvents | messagesLost | messagesSent |
| STOP |
|
|
|
|
| who | what | when | whom |
| 3 |
MSG_SENT | 28 | 2 |
| 0 |
MSG_ARRIVAL | 31 | 2 |
| who | what | when | whom |
| 1 |
MSG_ARRIVAL | 19 | 2 |
At this point the simulation breaks out of its loop.
___|______|______|_____|______|_____|______|_____|______|______|_____|__
0 5 10 15 20 25 30 35 40 45 50