-
Notifications
You must be signed in to change notification settings - Fork 171
Expand file tree
/
Copy pathch-simple-modules.tex
More file actions
5371 lines (4284 loc) · 208 KB
/
ch-simple-modules.tex
File metadata and controls
5371 lines (4284 loc) · 208 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\chapter{Simple Modules}
\label{cha:simple-modules}
\index{module!simple}
\textit{Simple modules} are the active components in the model.
Simple modules are programmed in C++, using the {\opp} class
library. The following sections contain a brief introduction
to discrete event simulation in general, explain how its concepts are
implemented in {\opp}, and give an overview and practical advice
on how to design and code simple modules.
\section{Simulation Concepts}
\label{sec:simple-modules:simulation-concepts}
This section contains a very brief introduction to how discrete
event simulation (DES) works, in order to introduce terms we'll use
when explaining {\opp} concepts\index{simulation!concepts} and
implementation.
\subsection{Discrete Event Simulation}
\label{sec:simple-modules:discrete-event-simulation}
A \textit{discrete event system} is a system where state changes
(events\index{events}) happen at discrete instances in time, and events take zero time
to happen. It is assumed that nothing (i.e. nothing interesting)
happens between two consecutive events, that is, no state change takes
place in the system between the events. This is in contrast to
\textit{continuous} systems where state changes are continuous.
Systems that can be viewed as discrete event systems can be modeled
using discrete event simulation\index{discrete event simulation}, DES.
For example, computer networks are usually viewed as discrete
event systems. Some of the events are:
\begin{itemize}
\item start of a packet transmission
\item end of a packet transmission
\item expiry of a retransmission timeout
\end{itemize}
This implies that between two events such as \textit{start of a packet
transmission} and \textit{end of a packet transmission}, nothing
interesting happens. That is, the packet's state remains \textit{being
transmitted}. Note that the definition of ``interesting'' events and states always
depends on the intent and purposes of the modeler.
If we were interested in the transmission of individual bits, we would
have included something like \textit{start of bit transmission} and
\textit{end of bit transmission} among our events.
The time when events occur is often called \textit{event timestamp};
\index{event timestamp} with {\opp} we use the term
\textit{arrival time}\index{arrival time} (because in the class
library, the word ``timestamp'' is reserved for a user-settable
attribute in the event class). Time within the model is often called
\textit{simulation time}\index{simulation time}, \textit{model time}
\index{model!time}, or \textit{virtual time}\index{virtual time},
as opposed to real time\index{real time} or CPU time\index{CPU time},
which refer to how long the simulation program has been running and
how much CPU time it has consumed.
\subsection{The Event Loop}
\label{sec:simple-modules:event-loop}
Discrete event simulation maintains the set of future
events\index{future events} in a data structure often called
FES\index{FES} (Future Event Set) or FEL\index{FEL} (Future Event List).
Such simulators usually work according to the following pseudocode:
\begin{Verbatim}[commandchars=\\\{\}]
\textit{initialize -- this includes building the model and}
\textit{inserting initial events to FES}
\textit{while (FES not empty and simulation not yet complete)}
\textit{\{}
\textit{retrieve first event from FES}
\textit{t:= timestamp of this event}
\textbf{\textit{process event}}
\textit{(processing may insert new events in FES or delete existing ones)}
\textit{\}}
\textit{finish simulation (write statistical results, etc.)}
\end{Verbatim}
The initialization step usually builds the data structures
representing the simulation model, calls any user-defined
initialization code, and inserts initial events\index{initial events}
into the FES\index{FES} to ensure that the simulation can start. Initialization
strategies can differ considerably from one simulator to another.
The subsequent loop consumes events from the FES\index{FES} and processes
them. Events are processed in strict timestamp order
to maintain causality, that is, to ensure that no current event may have
an effect on earlier events.
Processing an event involves calls to user-supplied code. For example,
using the computer network simulation example, processing a ``timeout
expired'' event may consist of re-sending a copy of the network
packet, updating the retry count, scheduling another ``timeout''
event, and so on. The user code may also remove events from the FES\index{FES},
for example, when canceling timeouts.
The simulation stops when there are no events left (this rarely happens
in practice) or when it isn't necessary for the simulation
to run further because the model time or the CPU time has reached
a given limit, or because the statistics have reached the desired
accuracy. At this time, before the program exits, the user
will typically want to record statistics into output files.
\subsection{Events and Event Execution Order in {\opp}}
\label{sec:simple-modules:events-in-opp}
{\opp} uses messages\index{message} to represent
events\index{events}.\footnote{For all practical purposes. Note that there
is a class called \cclass{cEvent} that \cclass{cMessage} subclasses from,
but it is only used internally to the simulation kernel.}
Messages are represented by instances of the \cclass{cMessage} class
and its subclasses. Messages are sent from one module to another -- this
means that the place where the ``event will occur'' is the
\textit{message's destination module}, and the model time when the
event occurs is the \textit{arrival time}\index{arrival time} of the
message. Events like ``timeout expired'' are implemented by the
module sending a message to itself.
Events are consumed from the FES\index{FES} in arrival time order, to
maintain causality. More precisely, given two messages, the following
rules apply:
\begin{enumerate}
\item The message with the \tbf{earlier arrival time} is executed
first. If arrival times are equal,
\item the one with the \tbf{higher scheduling priority} (smaller
numeric value) is executed first. If priorities are the same,
\item the one \tbf{scheduled/sent earlier} is executed first.
\end{enumerate}
\textit{Scheduling priority}\index{message!priority} is a user-assigned integer
attribute of messages.
\subsection{Simulation Time}
\label{sec:simple-modules:simulation-time}
The current simulation time can be obtained with the \ttt{simTime()} function.
Simulation time in {\opp} is represented by the C++ type \fvar{simtime\_t},
which is by default a typedef to the \ttt{SimTime} class.
\ttt{SimTime} class stores simulation time in a 64-bit integer,
using decimal fixed-point representation. The resolution is controlled
by the \textit{scale exponent} global configuration variable; that is,
\ttt{SimTime} instances have the same resolution. The exponent can be
chosen between -18 (attosecond resolution) and 0 (seconds).
Some exponents with the ranges they provide are shown in the following table.
\begin{center}
\begin{tabular}{ | r | r | c | }
\hline
Exponent & Resolution & Approx. Range \\ \hline
-18 & $10^{-18}$s (1 as) & $\pm 9.22$s \\
-15 & $10^{-15}$s (1 fs) & $\pm 153.72$ minutes \\
-12 & $10^{-12}$s (1 ps) & $\pm 106.75$ days \\
-9 & $10^{-9}$s (1 ns) & $\pm 292.27$ years \\
-6 & $10^{-6}$s (1 us) & $\pm 292271$ years \\
-3 & $10^{-3}$s (1 ms) & $\pm 2.9227e8$ years \\
0 & 1s & $\pm 2.9227e11$ years \\ \hline
\end{tabular}
\end{center}
Note that although simulation time cannot be negative, it is still useful to
be able to represent negative numbers because they often arise
during the evaluation of arithmetic expressions.
There is no implicit conversion from \ttt{SimTime} to \ttt{double}, mostly
because it would conflict with overloaded arithmetic operations of \ttt{SimTime};
use the \ffunc{dbl()} method of \ttt{SimTime} or the \ffunc{SIMTIME\_DBL()} macro
to convert. To reduce the need for \ffunc{dbl()}, several functions and methods
have overloaded variants that directly accept \ttt{SimTime}, for example,
\ffunc{fabs()}, \ffunc{fmod()}, \ffunc{div()}, \ffunc{ceil()}, \ffunc{floor()},
\ffunc{uniform()}, \ffunc{exponential()}, and \ffunc{normal()}.
Other useful methods of \ttt{SimTime} include \ffunc{str()},
which returns the value as a string; \ffunc{parse()}, which converts a
string to \ttt{SimTime}; \ffunc{raw()}, which returns the
underlying 64-bit integer; \ffunc{getScaleExp()}, which returns the
global scale exponent; \ffunc{isZero()}, which tests whether the
simulation time is 0; and \ffunc{getMaxTime()}, which returns the maximum
simulation time that can be represented at the current scale exponent.
Zero and the maximum simulation time are also accessible
via the \fmac{SIMTIME\_ZERO} and \fmac{SIMTIME\_MAX} macros.
\begin{cpp}
// 340 microseconds in the future, truncated to the millisecond boundary
simtime_t timeout = (simTime() + SimTime(340, SIMTIME_US)).trunc(SIMTIME_MS);
\end{cpp}
\begin{note}
Converting a \ttt{SimTime} to \ttt{double} may lose precision because
\ttt{double} only has a 52-bit mantissa.
Earlier versions of {\opp} used \ttt{double} for the simulation
time, but that caused problems in long simulations that relied on fine-grained timing,
for example, MAC protocols. Other problems were the accumulation of
rounding errors, and non-associativity (often $(x+y)+z \neq x+(y+z)$, see
~\cite{Goldberg91what}) which meant that two \ttt{double} simulation
times could not be reliably compared for equality.
\end{note}
\subsection{FES Implementation}
\label{sec:simple-modules:fes-implementation}
The implementation of the FES\index{FES} is a crucial factor in the
performance of a discrete event simulator. In {\opp}, the FES is
replaceable, and the default FES implementation uses \textit{binary
heap}\index{binary heap} as the data structure. Binary heap is generally
considered to be the best FES algorithm for discrete event simulation as
it provides a good, balanced performance for most workloads. (Exotic data
structures like \textit{skiplist}\index{skiplist} may perform better than
heap in some cases.)
\section{Components, Simple Modules, Channels}
\label{sec:simple-modules:simple-modules-in-opp}
{\opp} simulation models are composed of modules and connections. Modules
may be simple (atomic) modules or compound modules; simple modules are the
active components in a model, and their behavior is defined by the user as
C++ code. Connections may have associated channel objects. Channel objects
encapsulate channel behavior: propagation and transmission time modeling,
error modeling, and possibly others. Channels are also programmable in C++
by the user.
Modules and channels are represented with the \cclass{cModule} and \cclass{cChannel}
classes, respectively. \cclass{cModule} and \cclass{cChannel} are both
derived from the \cclass{cComponent} class.
The user defines simple module types by subclassing \cclass{cSimpleModule}.
Compound modules are instantiated with \cclass{cModule}, although
the user can override it with \fprop{@class} in the NED file, and can even
use a simple module C++ class (i.e. one derived from \cclass{cSimpleModule})
for a compound module.
The \cclass{cChannel}'s subclasses include the three built-in channel
types: \cclass{cIdealChannel}, \cclass{cDelayChannel}, and
\cclass{cDatarateChannel}. The user can create new channel types
by subclassing \cclass{cChannel} or any other channel class.
The following inheritance diagram illustrates the relationship
of the classes mentioned above.
\begin{figure}[htbp]
\begin{center}
\includesvg[scale=0.8]{figures/component-inheritance}
\caption{Inheritance of component, module, and channel classes}
\end{center}
\end{figure}
Simple modules and channels can be programmed by redefining certain
member functions and providing your own code in them. Some of those
member functions are declared on \cclass{cComponent}, the common base
class of channels and modules.
\cclass{cComponent} has the following member functions meant for
redefining in subclasses:
\begin{itemize}
\item \ffunc{initialize()}. This method is invoked after {\opp} has
set up the network (i.e., created modules and connected them according
to the definitions) and provides a place for initialization code.
\item \ffunc{finish()} is called when the simulation has terminated
successfully, and it is recommended to use it for recording summary
statistics.
\end{itemize}
\ffunc{initialize()} and \ffunc{finish()}, together with \ffunc{initialize()}'s
variants for multi-stage initialization, will be covered in detail in
section \ref{sec:simple-modules:initialize-and-finish}.
In {\opp}, events occur inside simple modules\index{module!simple}.
Simple modules encapsulate C++ code that generates events and reacts to events,
implementing the behavior of the module.
To define the dynamic behavior of a simple module, one of the following
member functions needs to be overridden:
\begin{itemize}
\item \ffunc[handleMessage()]{handleMessage(cMessage *msg)}. It
is invoked with the message as a parameter whenever the
module receives a message. \ffunc{handleMessage()} is
expected to \textit{process} the message and then return.
Simulation time never elapses inside \ffunc{handleMessage()} calls, only between them.
\item \ffunc{activity()} is started as a coroutine\footnote{Cooperatively
scheduled thread, explained later.} at the beginning of the simulation, and
it runs until the end of the simulation (or until the function
returns or otherwise terminates). Messages are obtained with
\ffunc{receive()} calls. Simulation time elapses inside
\ffunc{receive()} calls.
\end{itemize}
Modules written with \ffunc{activity()} and \ffunc{handleMessage()} can be
freely mixed within a simulation model. Generally, \ffunc{handleMessage()}
should be preferred to \ffunc{activity()}, due to scalability and other
practical reasons. The two functions will be described in detail in sections
\ref{sec:simple-modules:handlemessage} and \ref{sec:simple-modules:activity},
including their advantages and disadvantages.
The behavior of channels can also be modified by redefining member functions.
However, the channel API is slightly more complicated than that of simple
modules, so we'll describe it in a later section (\ref{sec:simple-modules:channels}).
Last, let us mention \ffunc{refreshDisplay()}, which is related to updating
the visual appearance of the simulation when run under a graphical user
interface. \ffunc{refreshDisplay()} is covered in the chapter that deals
with simulation visualization (\ref{sec:graphics:refreshdisplay}).
\begin{note}
\ffunc{refreshDisplay()} has been added in {\opp} 5.0. Until then,
visualization-related tasks were usually implemented as part of
\ffunc{handleMessage()}. \ffunc{refreshDisplay()} provides
a far superior and more efficient solution.
\end{note}
\section{Defining Simple Module Types}
\label{sec:simple-modules:defining-simple-modules}
\subsection{Overview}
\label{sec:simple-modules:defining:overview}
As mentioned before, a simple module\index{module!simple} is nothing more
than a C++ class which needs to be subclassed from \cclass{cSimpleModule},
with one or more virtual member functions redefined to define its behavior.
The class needs to be registered with {\opp} via the \fmac{Define\_Module()} macro.
The \fmac{Define\_Module()} line should always be placed in \ttt{.cc} or \ttt{.cpp}
files and not in the header file (\ttt{.h}), because the compiler generates code from it.
The following \ttt{HelloModule} is one of the simplest simple modules that can be written.
(We could have omitted the \ttt{initialize()} method as well to make it even smaller,
but then how would it say Hello?) Note the use of \cclass{cSimpleModule} as the base class,
and the \fmac{Define\_Module()} line.
\begin{cpp}
// file: HelloModule.cc
#include <omnetpp.h>
using namespace omnetpp;
class HelloModule : public cSimpleModule
{
protected:
virtual void initialize();
virtual void handleMessage(cMessage *msg);
};
// register module class with {\opp}
Define_Module(HelloModule);
void HelloModule::initialize()
{
EV << "Hello World!\n";
}
void HelloModule::handleMessage(cMessage *msg)
{
delete msg; // just discard everything we receive
}
\end{cpp}
In order to refer to this simple\index{module!simple} module type in NED files,
an associated NED declaration is also needed, which might look like this:
\begin{ned}
// file: HelloModule.ned
simple HelloModule
{
gates:
input in;
}
\end{ned}
\subsection{Constructor}
\label{sec:simple-modules:module-ctor}
Simple modules are never directly instantiated by the user, but rather by
the simulation kernel. This means that arbitrary constructors cannot be used:
the signature must be what is expected by the simulation kernel.
Luckily, this contract is very simple: the constructor must be public and must take
no arguments:
\begin{cpp}
public:
HelloModule(); // constructor takes no arguments
\end{cpp}
\cclass{cSimpleModule} itself has two constructors:
\begin{enumerate}
\item \ttt{cSimpleModule()} -- constructor without arguments
\item \ttt{cSimpleModule(size\_t stacksize)} -- constructor that accepts the coroutine
stack size\index{module!stack size}\index{stack!size}
\end{enumerate}
The first version should be used with \ffunc{handleMessage()} simple modules,
and the second one with \ffunc{activity()} modules.
(With the latter, the \ffunc{activity()} method of the module class
runs as a coroutine\index{coroutine} that needs a separate CPU stack,
usually of 16..32K. This will be discussed in detail later.)
Passing zero stack size to the latter constructor also selects \ttt{handleMessage()}.
Therefore, the following constructor definitions are all correct and select
\ffunc{handleMessage()} to be used with the module:
\begin{cpp}
HelloModule::HelloModule() {...}
HelloModule::HelloModule() : cSimpleModule() {...}
\end{cpp}
It is also correct to omit the constructor altogether, because the
compiler-generated one is suitable too.
The following constructor definition selects \ffunc{activity()} to be used
with the module, with 16K of coroutine stack:
\begin{cpp}
HelloModule::HelloModule() : cSimpleModule(16384) {...}
\end{cpp}
% \begin{note}
% The \fmac{Module\_Class\_Members()} macro, which was deprecated starting from {\opp} 3.2,
% has been removed in version 4.0. When porting older simulation models,
% occurrences of this macro can simply be removed from the source code.
% \end{note}
\subsection{Initialization and Finalization}
\label{sec:simple-modules:initialize-and-finish}
\subsubsection{Basic Usage}
\label{sec:simple-modules:init-finish:basic-usage}
The \ffunc{initialize()} and \ffunc{finish()} methods are declared
as part of \cclass{cComponent} and provide the user with the opportunity
to run code at the beginning and successful termination of the simulation.
The reason \ffunc{initialize()} exists is that simulation-related code
cannot usually be placed in the simple module's
constructor\index{module!constructor}, because the simulation model is still
being set up when the constructor runs, and many required objects are not yet
available. In contrast, \ffunc{initialize()} is called just before the
simulation starts executing, when everything else has already been set up.
\ffunc{finish()} is used for recording statistics and is only called
when the simulation has terminated normally. It does not get called when
the simulation stops with an error message. The destructor always
gets called at the end, regardless of how the simulation stopped, but
at that time it is reasonable to assume that the simulation model has already been
partly destroyed.
Based on the above considerations, the following conventions exist
for these four methods:
\begin{description}
\item Constructor:
Set pointer members of the module class to \ttt{nullptr}; postpone all other
initialization tasks to \ffunc{initialize()}.
\item \ffunc{initialize()}:
Perform all initialization tasks: read module parameters, initialize
class variables, allocate dynamic data structures with \ttt{new},
and allocate and initialize self-messages (timers) if needed.
\item \ffunc{finish()}:
Record statistics. Do \tbf{not} \ttt{delete} anything or cancel timers --
all cleanup must be done in the destructor.
\item Destructor:
Delete everything that was allocated by \ttt{new} and is still held
by the module class. When deleting self-messages (timers), use the
\ffunc{cancelAndDelete(msg)} function! It is usually incorrect
to simply delete a self-message from the destructor, because it might be
in the scheduled events list. The \ffunc{cancelAndDelete(msg)} function
first checks for that and cancels the message before deletion if necessary.
\end{description}
{\opp} prints the list of unreleased objects at the end of the simulation.
When a simulation model displays \textit{"undisposed object ..."} messages, it indicates
that the corresponding module destructors need to be fixed. As a temporary measure, these
messages can be hidden by setting \ttt{print-undisposed=false} in the
configuration.
\begin{note}
The \ttt{perform-gc} configuration option has been removed in {\opp} 4.0.
Automatic garbage collection cannot be reliably implemented due to the
limitations of the C++ language.
\end{note}
\subsubsection{Invocation Order}
\label{sec:simple-modules:init-finish:invocation-order}
The \ffunc{initialize()} functions of the modules are invoked \textit{before}
the first event is processed, but \textit{after} the initial events (starter
messages\index{starter messages}) have been placed into the FES\index{FES} by
the simulation kernel.
Both simple and compound modules have \ffunc{initialize()} functions. The
\ffunc{initialize()} function of a compound module runs \textit{before} that of
its submodules.
The \ffunc{finish()} functions are called when the event loop\index{event loop}
has terminated, but only if it terminated normally.
\begin{note}
\ffunc{finish()} is not called if the simulation has terminated
with a runtime error.
\end{note}
The calling order for \ffunc{finish()} is the reverse of the order of
\ffunc{initialize()}: first the submodules, then the encompassing compound module.
\footnote{To provide an \ffunc{initialize()} function for a compound module,
\cclass{cModule} needs to be subclassed, and the new class needs to be used
for the compound module by adding the \ttt{@class(<classname>)} property to the NED declaration.}
The following pseudocode summarizes this:
\begin{Verbatim}[commandchars=\\\{\}]
\textit{perform simulation run:}
build network
(i.e., create the system module and its submodules recursively)
insert starter messages for all submodules using activity()
do callInitialize() on the system module
\textit{enter event loop // (described earlier)}
if (event loop terminated normally) // i.e., no errors
do callFinish() on the system module
clean up
callInitialize()
\{
call to user-defined initialize() function
if (module is compound)
for (each submodule)
do callInitialize() on the submodule
\}
callFinish()
\{
if (module is compound)
for (each submodule)
do callFinish() on the submodule
call to user-defined finish() function
\}
\end{Verbatim}
Keep in mind that \ffunc{finish()} is not always called, so it is not
a suitable place for cleanup code that should run every time the module is
deleted. \ffunc{finish()} is only appropriate for writing statistics,
result post-processing, and other operations that are intended to run only on
successful completion. Cleanup code should be placed in the
destructor\index{module!destructor}.
\subsubsection{Multi-Stage Initialization}
\label{sec:simple-modules:multi-stage-init}
In simulation models where one-stage initialization\index{initialization}
provided by \ffunc{initialize()} is insufficient, multi-stage
initialization\index{initialization!multi-stage} can be used. Modules have two
functions that can be redefined by the user:
\begin{cpp}
virtual void initialize(int stage);
virtual int numInitStages() const;
\end{cpp}
The initialization of modules is orchestrated in stages. It starts with the call
to \ttt{initialize(0)} for every module, initiating the first setup stage. Once
this is completed across all modules, the system proceeds to the next steps,
\ttt{initialize(1)}, \ttt{initialize(2)}, and so on, effectively allowing
modules to undergo additional configuration in a controlled, sequential order.
To effectively manage this sequential setup, each module must declare how many
initial stages it requires by overriding the \ffunc{numInitStages()} function.
For instance, if a module needs two phases of setup, this function should return
2. Subsequently, the module must also tailor the C++ \ffunc{initialize(int
stage)} function to specify the operations that occur at each stage, such as
handling specific setups at \textit{stage=0} and \textit{stage=1}. This
organized approach to initialization ensures that each module is systematically
readied according to its operational needs within the broader simulation
context.\footnote{Note the \ttt{const} in the \ttt{numInitStages()} declaration.
If you forget it, a \textit{different} function is created instead of
redefining the existing one in the base class, so the existing function
remains in effect and returns 1.}
The \ffunc{callInitialize()} function performs the full multi-stage initialization
for the module and all its submodules.
If the multi-stage initialization functions are not redefined, the
default behavior is single-stage initialization: the default
\ffunc{numInitStages()} returns 1, and the default \ttt{initialize(int stage)}
simply calls \ffunc{initialize()}.
% \subsubsection{``End-of-Simulation'' Event}
% \label{sec:simple-modules:end-of-simulation-event}
%
% The task of \ffunc{finish()} is implemented differently in some other simulators
% by introducing a special \textit{end-of-simulation}\index{end-of-simulation} event.
% This practice is not recommended because the simulation programmer has to
% code the models (often represented as FSMs) in a way that they can properly
% respond to end-of-simulation events in any state. This often makes the code unnecessarily complex.
% Therefore, {\opp} does not use an end-of-simulation event.
%
% This can also be seen in the design of the PARSEC\index{PARSEC}
% simulation language (UCLA). Its predecessor Maisie used
% end-of-simulation events, but -- as documented in the PARSEC manual --
% this led to awkward programming in many cases, so for PARSEC
% end-of-simulation events were dropped in favor of \ffunc{finish()}
% (which is called \ffunc{finalize()} in PARSEC).
\section{Adding Functionality to cSimpleModule}
\label{sec:simple-modules:handlemessage-and-activity}
This section discusses \cclass{cSimpleModule}'s previously mentioned
\ffunc{handleMessage()} and \ffunc{activity()} member functions, which are
intended to be redefined by the user.
\subsection{handleMessage()}
\label{sec:simple-modules:handlemessage}
\subsubsection{Function Called for Each Event}
\label{sec:simple-modules:handlemessage:overview}
The idea is that at each event\index{event} (message arrival), we
simply call a user-defined function. This function,
\ffunc{handleMessage(cMessage *msg)}, is a
virtual member function of \cclass{cSimpleModule} which does
nothing by default -- the user has to redefine it in subclasses
and add the message processing code.
The \ffunc{handleMessage()} function will be called for every message
that arrives at the module. The function should process the message
and return immediately after that. The simulation time is potentially
different in each call. No simulation time elapses within a call
to \ffunc{handleMessage()}.
The event loop inside the simulator handles both \ffunc{activity()}
and \ffunc{handleMessage()} simple modules, and it corresponds
to the following pseudocode:
\begin{Verbatim}[commandchars=\\\{\}]
\textit{while (FES not empty and simulation not yet complete)}
\{
retrieve first event from FES
t:= timestamp of this event
m:= module containing this event
if (m works with handleMessage())
\textbf{m->handleMessage( event )}
else // m works with activity()
transferTo( m )
\}
\end{Verbatim}
Modules with \ffunc{handleMessage()} are NOT started automatically:
the simulation kernel creates starter messages\index{starter messages}
only for modules with \ffunc{activity()}. This means that you have to
schedule self-messages\index{self-message} from the
\ffunc{initialize()} function if you want a \ffunc{handleMessage()}
simple module to start working ``by itself'', without first receiving
a message from other modules.
\subsubsection{Programming with handleMessage()}
\label{sec:simple-modules:handlemessage:programming}
To use the \ffunc{handleMessage()} mechanism in a simple module, you must
specify \textit{zero stack size}\index{zero stack size} for the module.
This is important because this tells {\opp} that you want to use
\ffunc{handleMessage()}, not \ffunc{activity()}.
Message/event related functions you can use in \ffunc{handleMessage()}:
\begin{itemize}
\item \ffunc{send()} family of functions -- to send messages to other modules
\item \ffunc{scheduleAt()} -- to schedule an event (the module ``sends a message to itself'')
\item \ffunc{cancelEvent()} -- to delete an event scheduled with \ffunc{scheduleAt()}
\end{itemize}
The \ffunc{receive()} and \ffunc{wait()} functions cannot be used in
\ffunc{handleMessage()} because they are coroutine-based by nature, as
explained in the section about \ffunc{activity()}.
You have to add data members to the module class for every piece
of information you want to preserve. This information cannot
be stored in local variables of \ffunc{handleMessage()} because they
are destroyed when the function returns. Also, they cannot be
stored in static variables in the function (or the class) because
they would be shared between all instances of the class.
Data members to be added to the module class will typically include
things like:
\begin{itemize}
\item state (e.g. IDLE/BUSY, CONN\_DOWN/CONN\_ALIVE/...)
\item other variables which belong to the state of the module: retry
counts, packet queues, etc.
\item values retrieved/computed once and then stored: values of module
parameters, gate indices, routing information, etc.
\item pointers of message objects created once and then reused for
timers, timeouts, etc.
\item variables/objects for statistics collection
\end{itemize}
These variables are often initialized from the \ffunc{initialize()}
method because the information needed to obtain the initial value
(e.g. module parameters) may not yet be available at the time the
module constructor runs.
Another task to be done in \ffunc{initialize()} is to schedule
initial event(s)\index{events!initial} which trigger the first call(s)
to \ffunc{handleMessage()}. After the first call,
\ffunc{handleMessage()} must take care to schedule further events for
itself so that the ``chain'' is not broken. Scheduling events is not
necessary if your module only has to react to messages coming from
other modules.
\ffunc{finish()} is normally used to record statistics information
accumulated in data members of the class at the end of the simulation.
\subsubsection{Application Area}
\label{sec:simple-modules:handlemessage:application-area}
\ffunc{handleMessage()} is in most cases a better choice than \ffunc{activity()}:
\begin{enumerate}
\item When you expect the module to be used in large simulations
involving several thousand modules. In such cases, the module stacks
required by \ffunc{activity()} would simply consume too much memory.
\item For modules that maintain little or no state information,
such as packet sinks, \ffunc{handleMessage()} is more convenient to program.
\item Other good candidates are modules with a large state space and
many arbitrary state transition possibilities (i.e. where there
are many possible subsequent states for any state). Such algorithms
are difficult to program with \ffunc{activity()} and
better suited for \ffunc{handleMessage()} (see rule of thumb
below). This is the case for most communication protocols.
\end{enumerate}
\subsubsection{Example 1: Protocol Models}
\label{sec:simple-modules:handlemessage:protocol-model-example}
Models of protocol layers in a communication network tend to have
a common structure on a high level because fundamentally they all have to react
to three types of events: messages arriving from higher layer protocols
(or apps), messages arriving from lower layer protocols (from the network),
and various timers and timeouts (self-messages).
This usually results in the following source code pattern:
\begin{cpp}
class FooProtocol : public cSimpleModule
{
protected:
// state variables
// ...
virtual void processMsgFromHigherLayer(cMessage *packet);
virtual void processMsgFromLowerLayer(FooPacket *packet);
virtual void processTimer(cMessage *timer);
virtual void initialize();
virtual void handleMessage(cMessage *msg);
};
// ...
void FooProtocol::handleMessage(cMessage *msg)
{
if (msg->isSelfMessage())
processTimer(msg);
else if (msg->arrivedOn("fromNetw"))
processMsgFromLowerLayer(check_and_cast<FooPacket *>(msg));
else
processMsgFromHigherLayer(msg);
}
\end{cpp}
The functions \ttt{processMsgFromHigherLayer()}, \ttt{processMsgFromLowerLayer()},
and \ttt{processTimer()} are then usually split further: there are separate
methods to process separate packet types and separate timers.
\subsubsection{Example 2: Simple Traffic Generators and Sinks}
\label{sec:simple-modules:handlemessage:traffic-generator-example}
The code for simple packet generators and sinks programmed with
\ffunc{handleMessage()} might be as simple as the following pseudocode:
\begin{cpp}
PacketGenerator::handleMessage(msg)
{
create and send out a new packet;
schedule msg again to trigger next call to handleMessage;
}
PacketSink::handleMessage(msg)
{
delete msg;
}
\end{cpp}
Note that \textit{PacketGenerator} will need to redefine \ffunc{initialize()}
to create \textit{m} and schedule the first event.
The following simple module generates packets with exponential
inter-arrival time. (Some details in the source haven't been
discussed yet, but the code is probably understandable nevertheless.)
\begin{cpp}
class Generator : public cSimpleModule
{
public:
Generator() : cSimpleModule() {}
protected:
virtual void initialize();
virtual void handleMessage(cMessage *msg);
};
Define_Module(Generator);
void Generator::initialize()
{
// schedule first sending
scheduleAt(simTime(), new cMessage);
}
void Generator::handleMessage(cMessage *msg)
{
// generate & send packet
cMessage *pkt = new cMessage;
send(pkt, "out");
// schedule next call
scheduleAt(simTime()+exponential(1.0), msg);
}
\end{cpp}
\subsubsection{Example 3: Bursty Traffic Generator}
\label{sec:simple-modules:handlemessage:bursty-trafgen-example}
A bit more realistic example is to rewrite our Generator to create
packet bursts, each consisting of \ttt{burstLength} packets.
We add some data members to the class:
\begin{itemize}
\item \ttt{burstLength} will store the parameter that specifies how many
packets a burst must contain,
\item \ttt{burstCounter} will count how many packets are left to be sent
in the current burst.
\end{itemize}
The code:
\begin{cpp}
class BurstyGenerator : public cSimpleModule
{
protected:
int burstLength;
int burstCounter;
virtual void initialize();
virtual void handleMessage(cMessage *msg);
};
Define_Module(BurstyGenerator);
void BurstyGenerator::initialize()
{
// init parameters and state variables
burstLength = par("burstLength");
burstCounter = burstLength;
// schedule first packet of first burst
scheduleAt(simTime(), new cMessage);
}
void BurstyGenerator::handleMessage(cMessage *msg)
{
// generate & send packet
cMessage *pkt = new cMessage;
send(pkt, "out");
// if this was the last packet of the burst
if (--burstCounter == 0) {
// schedule next burst
burstCounter = burstLength;
scheduleAt(simTime()+exponential(5.0), msg);
}
else {
// schedule next sending within burst
scheduleAt(simTime()+exponential(1.0), msg);
}
}
\end{cpp}
\subsubsection{Pros and Cons of Using \ffunc{handleMessage()}}
\label{sec:simple-modules:handlemessage:pros-and-cons}
Pros:
\begin{itemize}
\item consumes less memory: no separate stack needed for simple modules
\item fast: function call is faster than switching between coroutines\index{coroutine}
\end{itemize}
Cons:
\begin{itemize}
\item local variables cannot be used to store state information
\item need to redefine \ffunc{initialize()}
\end{itemize}
Usually, \ffunc{handleMessage()} should be preferred over \ffunc{activity()}.
% \subsubsection{Other Simulators}
% \label{sec:simple-modules:handlemessage:other-simulators}
%
% Many simulation packages use a similar approach, often with
% something like a state machine\index{finite state machine}
% (FSM\index{FSM}) on top of it. These systems include:
% \begin{itemize}
% \item OPNET$^{TM}$, which uses FSMs designed using a graphical editor
% \item NetSim++, which clones OPNET's approach
% \item SMURPH (University of Alberta), which defines a (somewhat eclectic)
% language to describe FSMs and uses a precompiler to turn it
% into C++ code
% \item Ptolemy (UC Berkeley), which uses a similar method
% \end{itemize}
%
% {\opp}'s FSM\index{FSM} support is described in the next section.
\subsection{activity()}
\label{sec:simple-modules:activity}
\subsubsection{Process-Style Description}
\label{sec:simple-modules:activity:overview}
With \ffunc{activity()}, a simple module can be coded much like an
operating system process or thread. One can wait for an incoming message
(event) at any point in the code, suspend the execution for some time
(model time!), etc. When the \ffunc{activity()} function exits, the module
is terminated (the simulation can continue if there are other modules
that can run).
The most important functions that can be used in \ffunc{activity()} are
(they will be discussed in detail later):
\begin{itemize}
\item \ffunc{receive()} -- to receive messages (events)
\item \ffunc{wait()} -- to suspend execution\index{suspend execution}
for some time (model time)
\item \ffunc{send()} family of functions -- to send messages to other
modules
\item \ffunc{scheduleAt()} -- to schedule an event (the module "sends
a message to itself")
\item \ffunc{cancelEvent()} -- to delete an event scheduled with
\ffunc{scheduleAt()}
\item \ffunc{end()} -- to finish execution of this module (same as
exiting the \ffunc{activity()} function)
\end{itemize}
The \ffunc{activity()} function normally contains an infinite loop,
with at least a \ffunc{wait()} or \ffunc{receive()} call in its body.
\subsubsection{Application Area}
\label{sec:simple-modules:activity:application-area}
In general, you should prefer \ffunc{handleMessage()} to \ffunc{activity()}.
The main problem with \ffunc{activity()} is that it does not scale because