OMNeT++ User
OMNeT++ User
by András Varga
Document History
iii
OMNeT++ Manual –
iv
OMNeT++ Manual – CONTENTS
Contents
Contents v
1 Introduction 1
1.1 What is OMNeT++? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Where is OMNeT++ in the world of simulation tools? . . . . . . . . . . . . . . . 2
1.3 Organization of this manual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.5 Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 Overview 7
2.1 Modeling concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.1 Hierarchical modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.2 Module types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.1.3 Messages, gates, links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.1.4 Modeling of packet transmissions . . . . . . . . . . . . . . . . . . . . . . . 9
2.1.5 Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.1.6 Topology description method . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Programming the algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2.1 Creating simple modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2.2 Object mechanisms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2.3 Derive new classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2.4 Self-describing objects to ease debugging . . . . . . . . . . . . . . . . . . 11
2.3 Using OMNeT++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.1 Building and running simulations . . . . . . . . . . . . . . . . . . . . . . 11
2.3.2 What is what in the directories . . . . . . . . . . . . . . . . . . . . . . . . 13
v
OMNeT++ Manual – CONTENTS
vi
OMNeT++ Manual – CONTENTS
5 Simple Modules 53
5.1 Simulation concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.1.1 Discrete Event Simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.1.2 The event loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.1.3 Simple modules in OMNeT++ . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.1.4 Events in OMNeT++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.1.5 FES implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.2 Packet transmission modeling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.2.1 Delay, bit error rate, data rate . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.2.2 Multiple transmissions on links . . . . . . . . . . . . . . . . . . . . . . . . 57
5.3 Defining simple module types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
5.3.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
5.3.2 The module declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
5.3.3 Several modules, single NED interface . . . . . . . . . . . . . . . . . . . . 61
5.3.4 The class declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
5.3.5 Decomposing activity()/handleMessage() . . . . . . . . . . . . . . . . . . . 63
5.3.6 Using inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
5.3.7 Global variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
5.4 Adding functionality to cSimpleModule . . . . . . . . . . . . . . . . . . . . . . . 65
5.4.1 activity() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
5.4.2 handleMessage() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
5.4.3 initialize() and finish() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.5 Finite State Machines in OMNeT++ . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.6 Sending and receiving messages . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
5.6.1 Sending messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.6.2 Broadcasts and retransmissions . . . . . . . . . . . . . . . . . . . . . . . 81
5.6.3 Delayed sending . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.6.4 Direct message sending . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.6.5 Receiving messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.6.6 The wait() function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.6.7 Modeling events using self-messages . . . . . . . . . . . . . . . . . . . . . 85
5.6.8 Stopping the simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
5.7 Accessing module parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.8 Accessing gates and connections . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.8.1 Gate objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
vii
OMNeT++ Manual – CONTENTS
6 Messages 97
6.1 Messages and packets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
6.1.1 The cMessage class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
6.1.2 Message encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
6.1.3 Information about the last sending . . . . . . . . . . . . . . . . . . . . . . 99
6.1.4 Context pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.1.5 Modeling packets and frames . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.1.6 Attaching parameters and objects . . . . . . . . . . . . . . . . . . . . . . 101
6.2 Message definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
6.2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
6.2.2 Declaring enums . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
6.2.3 Message declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
6.2.4 Inheritance, composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
6.2.5 Using existing C++ types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
6.2.6 Customizing the generated class . . . . . . . . . . . . . . . . . . . . . . . 111
6.2.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.2.8 What else is there in the generated code? . . . . . . . . . . . . . . . . . . 115
viii
OMNeT++ Manual – CONTENTS
ix
OMNeT++ Manual – CONTENTS
x
OMNeT++ Manual – CONTENTS
xi
OMNeT++ Manual – CONTENTS
References 219
Index 221
xii
OMNeT++ Manual – Introduction
Chapter 1
Introduction
• protocol modeling
• . . . modeling any other system where the discrete event approach is suitable.
An OMNeT++ model consists of hierarchically nested modules. The depth of module nesting
is not limited, which allows the user to reflect the logical structure of the actual system in
the model structure. Modules communicate with message passing. Messages can contain
arbitrarily complex data structures. Modules can send messages either directly to their
destination or along a predefined path, through gates and connections.
Modules can have parameters which are used for three main purposes: to customize module
behaviour; to create flexible model topologies (where parameters can specify the number of
modules, connection structure etc); and for module communication, as shared variables.
Modules at the lowest level of the module hierarchy are to be provided by the user, and they
contain the algorithms in the model. During simulation execution, simple modules appear
to run in parallel, since they are implemented as coroutines (sometimes termed lightweight
processes). To write simple modules, the user does not need to learn a new programming
language, but he/she is assumed to have some knowledge of C++ programming.
OMNeT++ simulations can feature different user interfaces for different purposes: debug-
ging, demonstration and batch execution. Advanced user interfaces make the inside of
1
OMNeT++ Manual – Introduction
the model visible to the user, allow him/her to start/stop simulation execution and to in-
tervene by changing variables/objects inside the model. This is very important in the devel-
opment/debugging phase of the simulation project. User interfaces also facilitate demonstra-
tion of how a model works.
The simulator as well as user interfaces and tools are portable: they are known to work on
Windows and on several Unix flavours, using various C++ compilers.
OMNeT++ also supports parallel simulation (as of OMNeT++ 2.3, currently this feature is
currently being redesigned.)
2
OMNeT++ Manual – Introduction
• The Chapters 1, 2 and 3 contain introductory material: some overview and an example
simulation.
3
OMNeT++ Manual – Introduction
• The second group of Chapters, 4, 5 and 7 are the programming guide. They present
the NED language, the simulation concepts and their implementation in OMNeT++,
explain how to write simple modules and describe the class library.
• The following chapters, 8, 9 and 10 deal with practical issues like building and running
simulations and analyzing results, and present the tools OMNeT++ has to support these
tasks.
• Finally, Chapter 12 explains the architecture and the internals of OMNeT++. This
chapter will be useful to those who want to extend the capabilities of the simulator or
want to embed it into a larger application.
1.4 History
The development of OMNeT++ started as a semester’s programming assignment at the Tech-
nical University of Budapest (BME) in 1992. The assignment (“creation of an object-oriented
discrete event simulation system in C++”) was handed out by Prof. Dr György Pongor, and
two students signed up: Ákos Kun and András Varga. The basis for the design was Mr.
Pongor’s existing simulation software written in Pascal, named OMNeT.
We started developing the code in Borland C++ 3.1. The idea of multiple runtime environ-
ments, a significant addition to the original OMNeT design, was there from the very begin-
ning. We used Turbo Vision (Borland’s then successful character-based GUI) for the first
‘graphical’ user interface.
In 1992, we submitted a paper about OMNeT++ to the student’s annual university conference
(named “TDK”) and won first prize in the “Software” section. Later we also won 1st prize
in the national “TDK”. Then the idea came to port OMNeT++ to Unix (first for AIX on an
RS/6000 with only 16MB RAM, later Linux), until all development was done in Linux and
BC3.1 could no longer be supported.
Well, here’s a brief list of events since then – maybe one time I’ll make up my mind to enhance
them to a whole story. . .
1994: XEnv (a GUI in pure MOTIF, superceded by Tkenv by now) was written as diploma
work
1994: used OPNET for several simulation projects. OPNET features (and flaws) gave lots of
ideas how to continue with OMNeT++.
1995: initial version of nedc was written by a group of exchange students from Delft
1996: initial version of PVM support was programmed by Zoltan Vass as diploma work
1997: started working on Tkenv
1997 Dec: added GNED
1997 Sept: web site set up, first public release
1997 Feb-1998 Sept: simulation projects for a small company in Hungary. We used a version
of OMNeT++.
1998 March: added Plove
1998 June: animation implemented in Tkenv
1998 Sept-1999 May: work at MeTechnology (later Brokat) in Leipzig
4
OMNeT++ Manual – Introduction
1.5 Authors
OMNeT++ has been developed mostly by András Varga at the Technical University of Bu-
dapest, Department of Telecommunications (BME-HIT).
András Varga BME-HIT, [email protected]
Since leaving the university in 1998, I’ve been doing the development in my free time.
Several people have worked for shorter periods (1..3 months) on different topics within OM-
NeT++. Credit for organizing this goes to Dr. György Pongor (BME-HIT, [email protected]),
my advisor at the University. Here is a more-or-less complete list of people:
Old NED compiler, 1992-93:
Ákos Kun BME
JAR compiler (now called NEDC), sample simulations; summer 1995:
Jan Heijmans TU Delft
Alex Paalvast TU Delft
Robert van der Leij TU Delft
New feaures, testing, new examples; fall 1995:
Maurits André TU Delft, [email protected]
George van Montfort TU Delft, [email protected]
Gerard van de Weerd TU Delft, [email protected]
JAR (NEDC) support for distributed execution:
Gábor Lencse BME-HIT, [email protected]
PVM support (as final project), spring 1996:
Zoltán Vass BME-HIT
P2 , k-split algorithms and more, from fall 1996:
Babak Fakhamzadeh TU Delft
We have to mention Dr. Leon Rothkranz from the Technical University of Delft whose work
made it possible for the Delft students to come to Budapest in 1995.
Several bugfixes and valuable suggestions for improvements came from the user community
of OMNeT++. It would be impossible to mention everyone here, and the list is constantly
growing – instead, the README file contains acknowledgements to those I can remember.
Since the summer of 2001, the OMNeT++ sources are kept in the CVS server at the Uni-
versity of Karlsruhe. Credit for setting up and maintaining the CVS server goes to Ulrich
Kaage.
The starting point of this manual was the 1995 report of Jan Heijmans, Alex Paalvast and
Robert van der Leij.
5
OMNeT++ Manual – Introduction
6
OMNeT++ Manual – Overview
Chapter 2
Overview
Modules that contain submodules are termed compound modules, as opposed simple modules
which are at the lowest level of the module hierarchy. Simple modules contain the algorithms
in the model. The user implements the simple modules in C++, using the OMNeT++ simula-
tion class library.
7
OMNeT++ Manual – Overview
Both simple and compound modules are instances of module types. While describing the
model, the user defines module types; instances of these module types serve as components
for more complex module types. Finally, the user creates the system module as an instance of
a previously defined module type; all modules of the network are instantiated as submodules
and sub-submodules of the system module.
When a module type is used as a building block, there is no distinction whether it is a simple
or a compound module. This allows the user to split a simple module into several simple
modules embedded into a compound module, or vica versa, aggregate the functionality of a
compound module into a single simple module, without affecting existing users of the module
type.
Module types can be stored in files separately from the place of their actual usage. This
means that the user can group existing module types and create component libraries. This
feature will be discussed later, in Chapter 9.
Due to the hierarchical structure of the model, messages typically travel through a series of
connections, to start and arrive in simple modules. Such series of connections that go from
simple module to simple module are called routes. Compound modules act as ‘cardboard
boxes’ in the model, transparently relaying messages between their inside and their outside
world.
8
OMNeT++ Manual – Overview
Connections can be assigned three parameters which facilitate the modeling of communica-
tion networks, but can be useful for other models too: propagation delay, bit error rate and
data rate, all three being optional. One can specify link parameters individually for each
connection, or define link types and use them throughout the whole model.
Propagation delay is the amount of time the arrival of the message is delayed by when it
travels through the channel.
Bit error rate speficifies the probability that a bit is incorrectly transmitted, and allows for
simple noisy channel modelling.
Data rate is specified in bits/second, and it is used for calculating transmission time of a
packet.
When data rates are in use, the sending of the message in the model corresponds to the
transmission of the first bit, and the arrival of the message corresponds to the reception
of the last bit. This model is not always applicable, for example protocols like Token Ring
and FDDI do not wait for the frame to arrive in its entirety, but rather start repeating its
first bits soon after they arrive – in other words, frames “flow through” the stations, being
delayed only a few bits. If you want to model such networks, the data rate modeling feature
of OMNeT++ cannot be used.
2.1.5 Parameters
Modules can have parameters. Parameters are used for three purposes:
Parameters can take string, numeric or pointer values; numeric values include expressions
using other parameters and calling C functions, random variables from different distribu-
tions, and values input interactively by the user.
Numeric-valued parameters can be used to construct topologies in a flexible way. Within a
compound module, parameters can define the number of submodules, number of gates, and
the way the internal connections are made.
Compound modules can pass parameters or expressions of parameters to their submodules.
Parameter passing can be done by value or by reference.
During simulation execution, if a module changes the value of a parameter taken by refer-
ence, the changed value propagates to other modules. This effect can be used to tune the
model or as a second means of module communication.
The user defines the structure of the model in NED language descriptions (Network Descrip-
tion).The NED language will be discussed in detail in Chapter 4.
9
OMNeT++ Manual – Overview
The objects are designed so that they can efficiently work together, creating a powerful frame-
work for simulation programming.
10
OMNeT++ Manual – Overview
of the time, the ownership mechanism works transparently; ownership only needs to be
explicitly managed when the user wants to do something non-typical.
The foreach mechanism allows one to enumerate the objects inside a container object in a
uniform way and do some operation on them. This feature which makes it possible to handle
many objects together. (The foreach feature is extensively used by the user interfaces with
debugging capability and the snapshot mechanism; see later.)
• NED language topology description(s) which describe the module structure with pa-
rameters, gates etc. They are files with .ned suffix. NED files can be written with any
text editor or using the GNED graphical editor.
• Simple modules sources. They are C++ files, with .h/.cc suffix.
• Simulation kernel. This contains the code that manages the simulation and the simu-
lation class library. It is written in C++, compiled and put together to form a library (a
file with .a or .lib extension)
• User interfaces. OMNeT++ user interfaces are used with simulation execution, to fa-
cilitate debugging, demonstration, or batch execution of simulations. There are several
11
OMNeT++ Manual – Overview
user interfaces, written in C++, compiled and put together into libraries (.a or .lib
files).
Simulation programs are built from the above components. First, the NED files are compiled
into C++ source code, using the NEDC compiler which is part of OMNeT++. Then all C++
sources are compiled and linked with the simulation kernel and a user interface to form a
simulation executable.
The simulation executable is a standalone program, thus it can be run on other machines
without OMNeT++ or the model files being present. When the program is started, it reads
in a configuration file (usually called omnetpp.ini); it contains settings that control how
the simulation is run, values for model parameters, etc. The configuration file can also pre-
scribe several simulation runs; in the simplest case, they will be executed by the simulation
program one after another.
The output of the simulation is written into data files: output vector files, output scalar files ,
and possibly the user’s own output files. OMNeT++ provides a GUI tool named Plove to view
and plot the contents of output vector files. But it is not expected that someone will process
the result files using OMNeT++ alone: output files are text files in a format which (maybe af-
ter some preprocessing using sed, awk or perl) can be read into math packages like Matlab
or its free equivalent Octave, or imported into spreadsheets like Excel. All these external pro-
grams have rich functionality for statistical analysis and visualization, and OMNeT++ does
not try to duplicate their efforts. This manual briefly describes some data plotting programs
and how to use them with OMNeT++.
User interfaces
The primary purpose of user interfaces is to make the inside of the model visible to the
user, to start/stop simulation execution, and possibly allow the user intervene by changing
variables/objects inside the model. This is very important in the development/debugging
phase of the simulation project. Just as important, a hands-on experience allows the user to
get a ‘feel’ about the model’s behaviour. A nice graphical user interface can also be used to
demonstrate how the model works internally.
The same simulation model can be executed with different user interfaces, without any
change in the model files themselves. The user would test and debug the simulation with
a powerful graphical user interface, and finally run it with a simple and fast user interface
that supports batch execution.
Component libraries
Module types can be stored in files separately from the place of their actual usage. This
means that the user can group existing module types and create component libraries.
A simulation executable can store several independent models that use the same set of sim-
ple modules. The user can specify in the configuration file which model he/she wants to
run. This allows one to build one large executable that contains several simulation models,
and distribute it as a standalone simulation tool. The flexibility of the topology description
language also supports this approach.
12
OMNeT++ Manual – Overview
Sample simulations are within the samples directory. Each of the sample directories con-
tain a network description (.ned file) and corresponding simple module code (.h, .cc files).
Makefiles are included.
13
OMNeT++ Manual – Overview
You may also find additional directories like msvc/, which contain integration components
for Microsoft Visual C++, etc.
14
OMNeT++ Manual – An Example: The Nim Game
Chapter 3
This chapter contains a full example program that can give you some basic idea of using
the simulator. An enhanced version of the Nim example can be found among the sample
programs.
Nim is an ancient game with two players and a bunch of sticks. The players take turns,
removing 1, 2, 3 or 4 sticks from the heap of sticks at each turn. The one who takes the last
stick is the loser.
Of course, building a model of the Nim game is not much of a simulation project, but it nicely
demonstrates the modeling approach used by OMNeT++.
Describing the model consists of two phases:
• topology description
• defining the operation of components
3.1 Topology
The game can be modelled in OMNeT++ as a network with three modules: the “game” (a
manager module) and two players. The modules will communicate by exchanging messages.
The “game” module keeps the current number of tokens and organizes the game; in each
turn, the player modules receives the number of tokens from the Game module and sends
back its move.
Player1, Player2 and Game are simple modules (e.g. they have no submodules.) Each
15
OMNeT++ Manual – An Example: The Nim Game
module is an instance of a module type. We’ll need a module type to represent the Game
module; let’s call it Game too.
We can implement two kinds of players: SmartPlayer, which knows the winning algo-
rithm, and SimplePlayer, which simply takes a random number of sticks. In our example,
Player1 will be a SmartPlayer and Player2 will be a SimplePlayer.
The enclosing module, Nim is a compound module (it has submodules). It is also defined as a
module type of which one instance is created, the system module.
Modules have input and output gates (the tiny boxes labeled in, out, fromPlayer1, etc.
in the figure). An input and an output gate can be connected: connections (or links) are
shown as in the figure as arrows. During the simulation, modules communicate by sending
messages through the connections.
The user defines the topology of the network in NED files.
We placed the model description in two files; the first file defines the simple module types
and the second one the system module.
The first file (NED keywords are typed in boldface):
//---------------------------------------------------------
// file: nim_mod.ned
// Simple modules in nim.ned
//---------------------------------------------------------
simple Game
parameters:
numSticks, // initial number of sticks
firstMove; // 1=Player1, 2=Player2
gates:
in:
fromPlayer1, // input and output gates
fromPlayer2; // for connecting to Player1/Player2
out:
toPlayer1,
toPlayer2;
endsimple
16
OMNeT++ Manual – An Example: The Nim Game
//-------------------------------------------------------------
// file: nim.ned
// Nim compound module + system module
//-------------------------------------------------------------
import "nim_mod";
module Nim
submodules:
game: Game
parameters:
numSticks = intuniform(21, 31),
firstMove = intuniform(1, 2);
player1: SmartPlayer;
player2: SimplePlayer;
connections:
player1.out --> game.fromPlayer1,
player1.in <-- game.toPlayer1,
player2.out --> game.fromPlayer2,
player2.in <-- game.toPlayer2;
endmodule
#include <stdio.h>
#include <string.h>
#include <time.h>
17
OMNeT++ Manual – An Example: The Nim Game
#include <omnetpp.h>
for (;;)
{
// messages have several fields; here, we’ll use the message
// kind member to store the number of sticks
cMessage *msgin = receive(); // receive message from Game
int numSticks = msgin->kind(); // extract message kind (an int)
// it hold the number of sticks
// still on the stack
delete msgin; // dispose of the message
18
OMNeT++ Manual – An Example: The Nim Game
The Game module first waits for a message from both players and extracts the message names
that are also the players’ names. Then it enters a loop, with the playerToMove variable
alternating between 1 and 2. With each iteration, it sends out a message with the current
number of sticks to the corresponding player and gets back the number of sticks taken by that
player. When the sticks are out, the module announces the winner and ends the simulation.
The source:
//-------------------------------------------------------------
// file: game.cc
// (part of NIM - an OMNeT++ demo simulation)
//-------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include <omnetpp.h>
// operation of Game:
void Game::activity()
{
// strings to store player names; player[0] is unused
char player[3][32];
19
OMNeT++ Manual – An Example: The Nim Game
do
{
ev << "Sticks left: " << numSticks << "\n";
ev << "Player " << playerToMove << " ("
<< player[playerToMove] << ") to move.\n";
if (playerToMove == 1)
send(msg, "toPlayer1");
else
send(msg, "toPlayer2");
msg = receive();
int sticksTaken = msg->kind();
delete msg;
numSticks -= sticksTaken;
playerToMove = 3 - playerToMove;
}
while (numSticks>0);
endSimulation();
}
#
# file: omnetpp.ini
#
[General]
network = nim
random-seed = 3
20
OMNeT++ Manual – An Example: The Nim Game
ini-warnings = false
[Cmdenv]
express-mode = no
Suppose we link the Nim simulation with the command line user interface. We get the
executable nim (nim.exe under Windows). When we run it, we’ll get the following screen
output:
% ./nim
Or:
C:\OMNeT++\samples\nim> nim
Sticks left: 29
Player 2 (SimplePlayer) to move.
SimplePlayer is taking 2 out of 29 sticks.
Player 2 (SimplePlayer) took 2 stick(s).
Sticks left: 27
Player 1 (SmartPlayer) to move.
SmartPlayer is taking 1 out of 27 sticks.
Player 1 (SmartPlayer) took 1 stick(s).
Sticks left: 26
[...]
Sticks left: 5
Player 1 (SmartPlayer) to move.
SmartPlayer is taking 4 out of 5 sticks.
Player 1 (SmartPlayer) took 4 stick(s).
Sticks left: 1
Player 2 (SimplePlayer) to move.
SimplePlayer is taking 1 out of 1 sticks.
Player 2 (SimplePlayer) took 1 stick(s).
21
OMNeT++ Manual – An Example: The Nim Game
22
OMNeT++ Manual – The NED Language
Chapter 4
A NED description can contain the following components, in arbitrary number or order:
• import directives
• channel definitions
• network definitions
The writer of the network description has to take care that no reserved words are used for
names. The reserved words of the NED language are:
23
OMNeT++ Manual – The NED Language
4.1.3 Identifiers
Identifiers are the names of modules, channels, networks, submodules, parameters, gates,
channel attributes and functions.
Identifiers must be composed of letters of the English alphabet (a-z, A-Z), numbers (0-9) and
the underscore “_”. Identifiers may only begin with a letter or the underscore. If you want to
begin an identifier with a digit, prefix the name you’d like to have with an underscore, e.g.
_3Com.
If you have identifiers that are composed of several words, the convention is to capitalize the
beginning of every word. Also, it is recommended that you begin the names of modules, chan-
nels and networks with a capital letter, and the names of parameters, gates and submodules
with a lower-case letter. Underscores are rarely used.
4.1.5 Comments
Comments can be placed anywhere in the NED file, with the usual C++ syntax: comments
begin with a double slash ‘//’, and last until the end of the line. Comments are ignored by the
NED compiler.
It is planned that future OMNeT++ versions will use comments for documentation genera-
tion, much like JavaDoc or Doxygen.
24
OMNeT++ Manual – The NED Language
channel ChannelName
//...
endchannel
Three attributes can be assigned values in the body of the channel declaration, all of them op-
tional: delay, error and datarate. delay is the propagation delay in (simulated) seconds;
error is the bit error rate that speficifies the probability that a bit is incorrectly transmitted;
and datarate is the channel bandwidth in bits/second, used for calculating transmission
time of a packet. The attributes can appear in any order.
The values must be constants, or expressions that do not contain external references (e.g.
names of module parameters). If you assign a random-valued expression (e.g. truncnor-
mal(0.005,0.001)), it will be evaluated and a new random number generated for each
packet transmission.
Example:
channel DialUpConnection
delay normal (0.004, 0.0018)
error 0.00001
datarate 14400
endchannel
simple SimpleModuleName
parameters:
//...
gates:
//...
endsimple
simple TrafficGen
parameters:
25
OMNeT++ Manual – The NED Language
interArrivalTime,
numOfMessages : const,
address : string;
gates: //...
endsimple
If the parameter type is omitted, numeric is assumed. Practically, this means that you only
need to explicitly specify the type for string, bool or char-valued parameters.
Note that the actual parameter values are given later, when the module is used as a building
block of a compound module type or as a system module.
Const parameters
When you declare a parameter to be const, it will be evaluated and replaced by the resulting
constant value at the beginning of the simulation. This can be important when the original
value was a random number or an expression. One is advised to write out the const keyword
for each parameter that should be constant.
Beware when using const and by-reference parameter passing (ref modifier, see later) at
the same time. Converting the parameter to constant can affect other modules and cause
errors that are difficult to discover.
simple DataLink
parameters: //...
gates:
in: fromPort, fromHigherLayer;
out: toPort, toHigherLayer;
endsimple
simple RoutingModule
parameters: //...
gates:
in: output[];
out: input[];
endsimple
The sizes of gate vectors are given later, when the module is used as a building block of a
26
OMNeT++ Manual – The NED Language
compound module type. Thus, every instance of the module can have gate vectors of different
sizes.
module CompoundModule
parameters:
//...
gates:
//...
submodules:
//...
connections:
//...
endmodule
27
OMNeT++ Manual – The NED Language
value, one could get a different value each time the parameter is accessed during building
the internals of the compound module, which is surely not what was meant.)
Example:
module Router
parameters:
packetsPerSecond : numeric,
bufferSize : numeric,
numOfPorts : const;
gates:
in: inputPort[];
out: outputPort[];
submodules: //...
connections: //...
endmodule
4.5.2 Submodules
Submodules are defined in the submodules: section of a compound module declaration.
Submodules are identified by names. By convention, submodule names begin with lower-
case letters.
Submodules are instances of a module type, either simple or compound – there is no distinc-
tion. The module type must be known to the NED compiler, that is, it must have appeared
earlier in the same NED file or have been imported from another NED file.
It is possible to define vectors of submodules, and the size of the vector may come from a
parameter value.
When defining submodules, you can assign values to their parameters, and if the correspond-
ing module type has gate vectors, you have to specify their sizes.
Example:
module CompoundModuleName
//...
submodules:
submodule1: ModuleType1
parameters:
//...
gatesizes:
//...
submodule2: ModuleType2
parameters:
//...
gatesizes:
//...
endmodule
Module vectors
28
OMNeT++ Manual – The NED Language
module BigCompoundModule
parameters:
size: const;
submodules:
submod1: Node[3]
//...
submod2: Node[size]
//...
submod3: Node[2*size+1]
//...
endmodule
module RoutingTestNetwork
parameters:
routingNodeType: string; // should hold the name
// of an existing module type
gates: //...
submodules:
1 Ifyou like, the above solution somewhat similar to polymorphism in object-oriented languages – RoutingN-
ode is like a “base class”, DistVecRoutingNode and AntNetRouting1Node are like “derived classes”, and the
routingNodeType parameter is like a “pointer to a base class” which may be downcast to specific types.
29
OMNeT++ Manual – The NED Language
The RoutingNode module type does not need to be implemented in C++, because no instance
of it is created; it is merely used to check the correctness of the NED file.
On the other hand, the actual module types that will be substituted (e.g. DistVecRout-
ingNode, AntNetRouting1Node,etc.) do not need to be declared in the NED files.
The like phrase lets you create families of modules that serve similar purposes and im-
plement the same interface (they have the same gates and parameters) and to use them
interchangeably in NED files.
module CompoundModule
parameters:
param1: numeric,
param2: numeric,
useParam1: bool;
submodules:
submodule1: Node
parameters:
p1 = 10,
p2 = param1+param2,
p3 = useParam1==true ? param1 : param2;
//...
endmodule
The expression syntax is very similar to C. Expressions may contain constants (literals) and
parameters of the compound module being defined. Parameters can be passed by value or
by reference. The latter means that the expression is evaluated at runtime each time its
value is accessed (e.g. from simple module code), opening up interesting possibilities for the
modeler. You can also refer to parameters of the already defined submodules, with the syntax
submodule.parametername (or submodule[index].parametername).
Expressions are described in detail in section 4.7.
30
OMNeT++ Manual – The NED Language
When a parameter does not receive a value inside NED files or in the configuration file
(omnetpp.ini), the user will be prompted to enter its value at the beginning of the simula-
tion. If you plan to make use of interactive prompting, you can specify a prompt text and a
default value.
The syntax is the following:
The third version is actually equivalent to simply and quietly leaving out the parameter from
the list of assignments, but you can use it to make it explicit that you do not want to assign
a value from within the NED file.
Examples:
parameters:
numProc = input(10, "Number of processors?"),
processingTime = input(10ms);
simple Node
gates:
in: inputs[];
out: outputs[];
endsimple
module CompoundModule
parameters:
numPorts: const;
submodules:
node1: Node
gatesizes:
inputs[2], outputs[2];
node2: Node
gatesizes:
inputs[numPorts], outputs[numPorts];
//...
endmodule
31
OMNeT++ Manual – The NED Language
module Tandem
parameters: count: const;
submodules:
node : Node [count]
parameters:
position = "middle";
parameters if index==0:
position = "beginning";
parameters if index==count-1:
position = "end";
gatesizes:
in[2], out[2];
gatesizes if index==0 || index==count-1:
in[1], in[1];
connections:
//...
endmodule
If the conditions are not disjoint and a parameter value or a gate size is defined twice, the
last definition will take effect, overwriting the former ones. Thus, values intended as defaults
should appear in the first sections.
4.5.7 Connections
The compound module definition specifies how the gates of the compound module and its
immediate sub-modules are connected.
You can connect two submodules or a submodule with its enclosing compound module. (For
completeness, you can also connect two gates of the compound module on the inside, but
this is rarely needed). This means that NED does not permit connections that span multiple
levels of hieararchy – this restriction enforces compound modules to be self-contained, and
thus promotes reusability. Gate directions must also be observed, that is, you cannot connect
two output gates or two input gates.
Only one-to-one connections are supported. One-to-many and many-to-one connections can
be achieved using simple modules that duplicate messages or merge message flows. The
rationale is that wherever such fan-in or fan-out occurs in a model, it is usually associated
with some processing anyway that makes it necessary to use simple modules.
A gate can only be connected once: if two connections refer to the same gate, a compilation
or runtime error will occur.
By default, NED expects every gate to be connected, resulting in a compilation or runtime er-
ror if an unconnected gate is found. This check can be turned off with the nocheck modifier,
described later in this section.
Connections are specified in the connections: section of a compound module definition. It
lists the connections, separated by semicolons.
Example:
module CompoundModule
parameters: //...
gates: //...
submodules: //...
connections:
node1.output --> node2.input;
32
OMNeT++ Manual – The NED Language
• simple (that is, no delay, bit error rate or data rate), can use a named channel, or a
channel given with delay, error and data rate values;
• single or multiple (loop) connection;
• conditional or non-conditional.
The source gate can be an output gate of a submodule or an input gate of the compound
module, and the destination gate can be an input gate of a submodule or an output gate of
the compound module.
If you do not specify a channel, the connection will have no propagation delay, no transmis-
sion delay and no bit errors:
In this case, the NED sources must contain the definition of the channel.
One can also specify the channel parameters directly:
Either of the parameters can be omitted and they can be in any order.
Loop connections
If submodule or gate vectors are used, it is possible to create more than one connection with
one statement. This is termed a multiple or loop connection.
A multiple connection is created with the for statement:
for i=0..4 do
sender.outGate[i] --> receiver[i].inGate
endfor;
The result of the above loop connection can be illustrated as depicted in Fig. 4.1.
One can place several connections in the body of the for statement, separated by semicolons.
One can create nested loops by specifying more than one indices in the for statement, with
the first variable forming the outermost loop.
33
OMNeT++ Manual – The NED Language
One can also use an index in the lower and upper bound expressions of the subsequent
indices:
Conditional connections
for i=0..n do
sender.outGate[i] --> receiver[i].inGate if i%2==0;
endfor;
The if condition is evaluated for each connection (in the above example, for each i value),
and the decision is made individually each time whether to create the the connection or not.
In the above example we connected every second gate. Conditions may also use random
variables, as shown in the next section.
By default, NED requires that all gates be connected. Since this check can be inconvenient
at times, it can be turned off using the nocheck modifier.
The following example generates a random subgraph of a full graph.
module RandomConnections
parameters: //..
gates: //..
submodules: //..
connections nocheck:
for i=0..n-1, j=0..n-1 do
node[i].out[j] --> node[j].in[i] if uniform(0,1)<0.3;
endfor;
endmodule
34
OMNeT++ Manual – The NED Language
When using nocheck, it is the simple modules’ responsibility not to send messages on gates
that are not connected.
Module module declarations (compound and simple module declarations) just define module
types. To actually get a simulation model that can be run, you need to write a network
definition.
A network definition declares a simulation model as an instance of a previously defined mod-
ule type. You’ll typically want to use a compound module type here, although it is also possi-
ble to program a model as a self-contained simple module and instantiate it as a “network”.
There can be several network definitions in your NED file or NED files. The simulation
program that uses those NED files will be able to run any of them; you typically select the
desired one in the config file (omnetpp.ini).
The syntax of a network definition is similar that of a submodule declaration:
Here, WirelessLAN is the name of previously defined compound module type, which pre-
sumably contains further compound modules of types WirelessHost, WirelessHub, etc.
Naturally, only compound module types without gates can be used in network definitions.
Just as for submodules, you do not need to assign values to all parameters. Unassigned pa-
rameters will get their values from the config file (omnetpp.ini) or interactively prompted
for.
4.7 Expressions
In the NED language there are a number of places where expressions are expected.
Expressions have a C-style syntax. They are built with the usual math operators; they can
use parameters taken by value or by reference; call C functions; contain random and input
values etc.
When an expression is used for a parameter value, it is evaluated each time the parameter
value is accessed (unless the parameter is declared const, see 4.4.1). This means that a
simple module querying a non-const parameter during simulation may get different values
each time (e.g. if the value involves a random variable, or it contains other parameters taken
by reference). Other expressions (including const parameter values) are evaluated only
once.
35
OMNeT++ Manual – The NED Language
4.7.1 Constants
Numeric and string constants
String constants
Time constants
Anywhere you would put numeric constants (integer or real) to mean time in seconds, you
can also specify the time in units like milliseconds, minutes or hours:
...
parameters:
propagationDelay = 560ms, // 0.560s
connectionTimeout = 6m 30s 500ms, // 390.5s
recoveryIntvl = 0.5h; // 30 min
Expressions can use the parameters of the enclosing compound module (the one being de-
fined) and of submodules defined earlier in NED file. The syntax for the latter is sub-
mod.param or submod[index].param.
You can refer to a compound module parameter called param in several ways: as param, ref
param, ancestor param, or ref ancestor param. They all have different semantics.
The first two variations, param and ref param lets you access the parameters of the com-
pound module being defined. In the third and fourth versions, the keyword ancestor means
that the parameter will be searched for upwards, in the module nesting hierarchy. Naturally,
this kind of reference can only be resolved at runtime, when the whole network has been built
up. The parameter which is found first is used. If no such parameter can be found in any of
the enclosing modules, it is a runtime error.
The ref and ref-less versions differ in how the parameter is taken: by value or by refer-
ence. If you take a parameter by reference, then runtime changes to that parameter will be
reflected in the assigned parameter: each time a simple module reads the parameter value,
the expression is evaluated, and you may get a different value. In contrast, if you take the
36
OMNeT++ Manual – The NED Language
2
parameter by value, then runtime changes do not affect the assigned parameter.
Reference parameters open up interesting possibilities for the modeler. For example, you
can define a parameter that at the highest level of the model, and let other modules take
it by reference – then if you change the parameter value at runtime (manually or from a
simple module), it will affect the whole model. You can use this arrangement to “tune” model
parameters at runtime, in search for an optimal setting.
In another setup, reference parameters can be used by to propagate status values to neigh-
bouring modules.
4.7.3 Operators
The set of operators supported in NED is similar to C/C++, with the following differences:
All values are represented as doubles. For the bitwise operators, doubles are converted to
unsigned long 3 using the C/C++ builtin conversion (type cast), the operation is performed,
then the result is converted back to double. Similarly, for the logical operators &&, || and
##, the operands are converted to bool using the C/C++ builtin conversion (type cast), the
operation is performed, then the result is converted back to double. For modulus (%), the
operands are converted to long.
Here’s the complete list of operators, in order of decreasing precendence:
Operator Meaning
-, !, ∼ unary minus, negation, bitwise complement
^ power-of
∗, /, % multiply, divide, modulus
+, - add, subtract
«, » bitwise shifting
&, |, # bitwise and, or, xor
== equal
!= not equal
>, >= greater, greater or equal
<, <= less, less or equal
&&, ||, ## logical operators and, or, xor
?: the C/C++ “inline if”
2 This also means that if an expression doesn’t contain any parameter taken by reference, the NED compiler may
have the possibility to evaluate the expression only once, at compile time. If an expression refers to parameters
taken by reference, only runtime evaluation can be used.
3 In case you are worried about long values being not accurately represented in doubles, this is not the case.
IEEE-754 doubles have 52 bit mantissas, and integer numbers in that range are represented without rounding
errors.
37
OMNeT++ Manual – The NED Language
module Compound
gates: in: fromgens[];
submodules:
proc: Processor[ sizeof(fromgens) ];
parameters: address = 10*(1+index);
connections:
for i = 0.. sizeof(fromgens)-1 do
in[i] --> proc[i].input;
endfor;
endmodule
Here, we create as many processors as there are input gates for this compound module in
the network. The address parameters of the processors are 10, 20, 30 etc.
4.7.5 Functions
In NED expressions, you can use the following mathematical functions:
• many of the C language’s <math.h> library functions: exp(), log(), sin(), cos(),
floor(), ceil(), etc.
• functions that generate random variables: uniform, exponential, normal and oth-
ers were already discussed.
Function Description
Continuous distributions
uniform(a, b, rng=0) uniform distribution in the range [a,b)
exponential(mean, rng=0) exponential distribution with the given mean
normal(mean, stddev, rng=0) normal distribution with the given mean and
standard deviation
truncnormal(mean, stddev, normal distribution truncated to nonnegative
rng=0) values
38
OMNeT++ Manual – The NED Language
If you do not specify the optional rng argument, the functions will use random number
generator 0.
Examples:
The above distributions are implemented with C functions, and you can easily add new ones
(see section 4.7.7). Your distributions will be treated in the same way as the built-in ones.
#include <omnetpp.h>
39
OMNeT++ Manual – The NED Language
Define_Function(average, 2);
The number 2 means that the average() function has 2 arguments. After this, the aver-
age() function can be used in NED files:
module Compound
parameter: a,b;
submodules:
proc: Processor
parameters: av = average(a,b);
endmodule
If your function takes parameters that are int or long or some other type which is not
double, you can create wrapper function that takes all doubles and does the conversion. In
this case you have to register the wrapper function with the Define_Function2() macro
which allows a function to be registered with a name different from the name of the function
that implements it. You can do the same if the return value differs from double.
#include <omnetpp.h>
long factorial(int k)
{
...
}
"p=100,100;b=60,10,rect;o=blue,black,2"
Parameters may be omitted also at the end and also inside the parameter list, like:
"p=100,100;b=,,rect;o=blue,black"
40
OMNeT++ Manual – The NED Language
"p=$xpos,$ypos;b=rect,60,10;o=$fillcolor,black,2"
Tag Meaning
p=xpos,ypos Place submodule at (xpos,ypos) pixel position,
with the origin being the top-left corner of the
enclosing module.
Defaults: an appropriate automatic layout is
where submodules do not overlap.
If applied to a submodule vector, ring or row lay-
out is selected automatically.
p=xpos,ypos,row,deltax Used for module vectors. Arranges submodules
in a row starting at (xpos,ypos), keeping deltax
distances.
Defaults: deltax is chosen so that submodules do
not overlap.
row may be abbreviated as r.
p=xpos,ypos,column,deltay Used for module vectors. Arranges submodules
in a column starting at (xpos,ypos), keeping
deltay distances.
Defaults: deltay is chosen so that submodules do
not overlap.
column may be abbreviated as col or c.
p=xpos,ypos,matrix, itemsper- Used for module vectors. Arranges submodules
row,deltax,deltay in a matrix starting at (xpos,ypos), at most
itemsperrow submodules in a row, keeping deltax
and deltay distances.
Defaults: itemsperrow=5, deltax,deltay are
chosen so that submodules do not overlap.
matrix may be abbreviated as m.
p=xpos,ypos,ring,width,height Used for module vectors. Arranges submodules
in an ellipse, with the top-left corner of its
bounding boxes at (xpos,ypos), with the width
and height.
Defaults: width=40, height=24
ring may be abbreviated as ri.
41
OMNeT++ Manual – The NED Language
Examples:
"p=100,60;i=workstation"
"p=100,60;b=30,30,rect;o=4"
Tag Meaning
p=xpos,ypos Place enclosing module at (xpos,ypos) pixel posi-
tion, with (0,0) being the top-left corner of the
window.
b=width,height,rect Display enclosing module as a rectangle with the
given height and width.
Defaults: width, height are chosen automatically
b=width,height,oval Display enclosing module as an ellipse with the
given height and width.
Defaults: width, height are chosen automatically
o=fillcolor,outlinecolor,borderwidth Specifies options for the rectangle or oval. Any
valid Tk color specification is accepted: English
color names or #rgb, #rrggbb format (where r,g,b
are hex digits).
Defaults: fillcolor=#8080ff (a lightblue), outline-
color=black, borderwidth=2
42
OMNeT++ Manual – The NED Language
Tag Meaning
m=auto Drawing mode. Specifies the exact placement of
m=north the connection arrow. The arguments can be ab-
m=west breviated as a,e,w,n,s.
m=east
m=south
m=manual,srcpx,srcpy,destpx,destpy The manual mode takes four parameters that
explicitly specify anchoring of the ends of the
arrow: srcpx, srcpy, destpx, destpy. Each value is
a percentage of the width/height of the
source/destination module’s enclosing rectangle,
with the upper-left corner being the origin. Thus,
m=m,50,50,50,50
would connect the centers of the two module rect-
angles.
o=color,width Specifies the appearance of the arrow. Any valid
Tk color specification is accepted: English color
names or #rgb, #rrggbb specification (where r,g,b
are hex digits).
Defaults: color=black, width=2
Examples:
"m=a;o=blue,3"
4.9.1 Examples
Example 1: Router
The following example contains a router module with the number of ports taken as param-
eter. The compound module is built using three module types: Application, RoutingModule,
DataLink. We assume that their definition is in a separate NED file which we will import.
import "modules";
module Router
parameters:
rteProcessingDelay, rteBuffersize,
numOfPorts: const;
gates:
in: inputPorts[];
out: outputPorts[];
submodules:
localUser: Application;
routing: RoutingModule
43
OMNeT++ Manual – The NED Language
parameters:
processingDelay = rteProcessingDelay,
buffersize = rteBuffersize;
gatesizes:
input[numOfPorts+1],
output[numOfPorts+1];
portIf: DataLink[numOfPorts]
parameters:
retryCount = 5,
windowSize = 2;
connections:
for i=0..numOfPorts-1 do
routing.output[i] --> portIf[i].fromHigherLayer;
routing.input[i] <-- portIf[i].toHigherLayer;
portIf[i].toPort --> outputPorts[i];
portIf[i].fromPort <-- inputPorts[i];
endfor;
routing.output[numOfPorts] --> localUser.input;
routing.input[numOfPorts] <-- localUser.output;
endmodule
Example 2: Chain
module Chain
parameters: count: const;
submodules:
node : Node [count]
gatesizes:
in[2], out[2];
gatesizes if index==0 || index==count-1:
in[1], out[1];
connections:
for i = 0..count-2 do
node[i].out[i!=0 ? 1 : 0] --> node[i+1].in[0];
node[i].in[i!=0 ? 1 : 0] <-- node[i+1].out[0];
endfor;
endmodule
One can use conditional connections to build a binary tree. The following NED code loops
through all possible node pairs, and creates the connections needed for a binary tree.
simple BinaryTreeNode
gates:
in: fromupper;
out: downleft;
out: downright;
endsimple
44
OMNeT++ Manual – The NED Language
module BinaryTree
parameters:
height: const;
submodules:
node: BinaryTreeNode [ 2^height-1 ];
connections nocheck:
for i = 0..2^height-2, j = 0..2^height-2 do
node[i].downleft --> node[j].fromupper if j==2*i+1;
node[i].downright --> node[j].fromupper if j==2*i+2;
endfor;
endmodule
Note that not every gate of the modules will be connected. By default, an unconnected gate
produces a run-time error message when the simulation is started, but this error message is
turned off here with the nocheck modifier. Consequently, it is the simple modules’ responsi-
bility not to send on a gate which is not leading anywhere.
An alert reader might notice that there is a better alternative to the above code. Each node
except the ones at the lowest level of the tree has to be connected to exactly two nodes, so we
can use a single loop to create the connections.
module BinaryTree2
parameters:
height: const;
submodules:
node: BinaryTreeNode [ 2^height-1 ];
connections nocheck:
for i=0..2^(height-1)-2 do
node[i].downleft --> node[2*i+1].fromupper;
node[i].downright --> node[2*i+2].fromupper;
endfor;
endmodule
Conditional connections can also be used to generate random topologies. The following code
generates a random subgraph of a full graph:
module RandomGraph
parameters:
count: const,
connectedness; // 0.0<x<1.0
submodules:
node: Node [count];
gatesizes: in[count], out[count];
connections nocheck:
for i=0..count-1, j=0..count-1 do
node[i].out[j] --> node[j].in[i]
if i!=j && uniform(0,1)<connectedness;
endfor;
endmodule
Note the use of the nocheck modifier here too, to turn off error messages given by the net-
work setup code for unconnected gates.
45
OMNeT++ Manual – The NED Language
This pattern takes a subset of the connections of a full graph. A condition is used to “carve
out” the necessary interconnection from the full graph:
The RandomGraph compound module (presented earlier) is an example of this pattern, but
the pattern can generate any graph where an appropriate condition(i,j) can be formulated.
For example, when generating a tree structure, the condition would return whether node j is
a child of node i or vica versa.
Though this pattern is very general, its usage can be prohibitive if the N number of nodes
is high and the graph is sparse (it has much fewer connections that N 2 ). The following two
patterns do not suffer from this drawback.
The pattern loops through all nodes and creates the necessary connections for each one. It
can be generalized like this:
The Hypercube compound module (to be presented later) is a clear example of this approach.
BinaryTree can also be regarded as an example of this pattern where the inner j loop is
unrolled.
The applicability of this pattern depends on how easily the rightNodeIndex(i,j) function can
be formulated.
for i=0..Nconnections-1 do
node[leftNodeIndex(i)].out[...] --> node[rightNodeIndex(i)].in[...];
endfor;
The pattern can be used if leftNodeIndex(i) and rightNodeIndex(i) mapping functions can be
sufficiently formulated.
The Serial module is an example of this approach where the mapping functions are extremely
simple: leftNodeIndex(i)=i and rightNodeIndex(i)=i+1. The pattern can also be used to create
a random subset of a full graph with a fixed number of connections.
46
OMNeT++ Manual – The NED Language
In the case of irregular structures where none of the above patterns can be employed, you
can resort to specifying constant submodule/gate vector sizes and explicitly listing all con-
nections, like you would do it in most existing simulators.
Topology templates are nothing more than compound modules where one or more submodule
types are left as parameters (using the like phrase of the NED language). You can write
such modules which implement mesh, hypercube, butterfly, perfect shuffle or other topolo-
gies, and you can use them wherever needed in you simulations. With topology templates,
you can reuse interconnection structure.
An example: hypercube
The hypercube topology template is the following (it can be placed into a separate file, e.g
hypercube.ned):
simple Node
gates:
out: out[];
in: in[];
endsimple
module Hypercube
parameters:
dim, nodetype;
submodules:
node: nodetype[2^dim] like Node
gatesizes:
out[dim], in[dim];
connections:
for i=0..2^dim-1, j=0..dim-1 do
node[i].out[j] --> node[i # 2^j].in[j]; // # is bitwise XOR
47
OMNeT++ Manual – The NED Language
endfor;
endmodule
When you create an actual hypercube, you substitute the name of an existing module type
(e.g. "Hypercube_PE") for the nodetype parameter. The module type implements the algo-
rithm the user wants to simulate and it must have the same gates that the Node type has.
The topology template code can be used through importing the file:
import "hypercube.ned";
simple Hypercube_PE
gates: out: out[]; in: in[];
endsimple
If you put the nodetype parameter to the ini file, you can use the same simulation model
to test e.g. several routing algorithms in a hypercube, each algorithm implemented with a
different simple module type – you just have to supply different values to nodetype, such as
"WormholeRoutingNode", "DeflectionRoutingNode", etc.
The two solutions have different advantages and disadvantages. The first is more useful in
the model development phase, while the second one is better for writing larger scale, more
productized simulation programs. In the next sections we examine both methods.
48
OMNeT++ Manual – The NED Language
nedtool -x wireless.ned
It generates wireless_n.xml. Several switches control the exact content and details of the
resulting XML as well as the amount of checks made on the input.
Converting the XML representation back to NED:
nedtool -n wireless.xml
nedtool wireless.ned
The resulting code is more compact and less redundant than the one created by nedc. As a
result, nedtool-created _n.cc C++ files compile much faster.
You can generate C++ code from the XML format, too:
nedtool wireless.xml
The opp_nedtool command uses XSLT to produce HTML documentation from the given NED
files, much like Javadoc or Doxygen. 4
opp_neddoc *.ned
The output file is called neddoc.html. opp_nedtool can be very useful in discovering and
understanding the structure of large models like the IP-Suite. On Unix, you’d use it like this:
4 You’re going to need xsltproc (part of libxml/libxslt) installed on your system. Since Gnome and KDE also heavily
rely on these components, there’s a good chance it is already present on your system.
49
OMNeT++ Manual – The NED Language
cd IPSuite
opp_neddoc ‘find . -name *.ned‘
// ---------------------------------------------------------------
// File: sample.ned
//
// This is a file comment. File comments reach from the top of
// the file till the last blank line above the first code line.
// ---------------------------------------------------------------
//
50
OMNeT++ Manual – The NED Language
Mouse Effect
In draw mode:
Drag out a rectangle in empty area: create new submodule
Drag from one submodule to another: create new connection
Click in empty area: switch to select/move mode
In select/move mode:
Click submodule/connection: select it
Ctrl-click submodule/conn.: add to selection
Click in empty area: clear selection
Drag a selected object: move selected objects
Drag submodule or connection: move it
Drag either end of connection: move that end
Drag corner of (sub)module: resize module
Drag starting in empty area: select enclosed submodules/connections
Del key delete selected objects
Both editing modes:
Right-click on module/submodule/connec- popup menu
tion:
Double-click on submodule: go into submodule
Click name label edit name
Drag&drop module type from the tree view create a submodule of that type
to the canvas
51
OMNeT++ Manual – The NED Language
52
OMNeT++ Manual – Simple Modules
Chapter 5
Simple Modules
The activities of simple modules are implemented by the user. The algorithms are pro-
grammed in C++, using the OMNeT++ class library. The following sections contain a short
introduction to discrete event simulation in general, how its concepts are implemented in
OMNeT++, and gives an overview and practical advice on how to design and code simple
modules.
This implies that between two events such as start of a packet transmission and end of a
packet transmission, nothing interesting happens. That is, the packet’s state remains being
transmitted. Note that the definition of “interesting” events and states always depends on
the intent and purposes of the person doing the modeling. If we were interested in the trans-
mission of individual bits, we would have included something like start of bit transmission
and end of bit transmission among our events.
53
OMNeT++ Manual – Simple Modules
The time when events occur is often called event timestamp ; with OMNeT++ we’ll say ar-
rival 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 simulation time, model
time or virtual time as opposed to real time or CPU time or which refers to how long the
simulation program has been running or how much CPU time it has consumed.
The first, initialization step usually builds the data structures representing the simulation
model, calls any user-defined initialization code, and inserts initial events into the FES to
ensure that the simulation can start. Initialization strategy can be quite different from one
simulator to another.
The subsequent loop consumes events from the FES and processes them. Events are pro-
cessed in strict timestamp order in order to maintain causality, that is, to ensure that no
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, for example when cancelling
timeouts.
Simulation stops when there are no more events left (this happens rarely 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 simulation programmer will typically want to
record statistics into output files.
54
OMNeT++ Manual – Simple Modules
useful by itself – you have to redefine some virtual member functions to make it do useful
work.
These member functions are the following:
• void initialize()
• void activity()
• void finish()
In the initialization step, OMNeT++ builds the network: it creates the necessary simple and
compound modules and connects them according to the NED definitions. OMNeT++ also calls
the initialize() functions of all modules.
The activity() and handleMessage() functions are called during event processing. This
means that the user will implement the model’s behavior in these functions. activity()
and handleMessage() implement different event processing strategies: for each simple
module, the user has to redefine exactly one of these functions. activity() is a coroutine-
based solution which implements the process interaction approach (coroutines are non-pre-
emptive [cooperative] threads). handleMessage() is a method that is called by the simu-
lation kernel when the module receives a message. Modules written with activity() and
handleMessage() can be freely mixed within a simulation model.
The finish() functions are called when the simulation terminates successfully. The most
typical use of finish() is the recording of statistics collected during simulation.
All these functions will be discussed later in detail.
1. the message with earlier arrival time is executed first. If arrival times are equal,
2. the one with smaller priority value is executed first. If priorities are the same,
55
OMNeT++ Manual – Simple Modules
you should take care that they are calculated in exactly the same way. Another possible
approach is to avoid equal arrival times, for example by adding/subtracting small values to
schedule times to ensure specific execution order (inorder_epsilon).
One may suggest introducing a small simtime_precision parameter in the simulation kernel
that would force t1 and t2 to be regarded equal if they are “very close” (if they differ less than
simtime_precision). However, in addition to the problem determining the correct value for
simtime_precision, this approach is likely to cause confusion in many cases.
Each of these parameters is optional. One can specify link parameters individually for each
connection, or define link types (also called channel types) once and use them throughout the
whole model.
The propagation delay is the amount of time the arrival of the message is delayed by when
it travels through the channel. Propagation delay is specified in seconds.
The bit error rate has influence on the transmission of messages through the channel. The
bit error rate is the probability that a bit is incorrectly transmitted. Thus, the probability
that a message of n bits length is transferred correctly is:
The message has an error flag which is set in case of transmission errors.
The data rate is specified in bits/second, and it is used for transmission delay calculation.
The sending time of the message normally corresponds to the transmission of the first bit,
and the arrival time of the message corresponds to the reception of the last bit (Fig. 5.1).
The above model is not applicable for modeling some protocols like Token Ring and FDDI
where the stations repeat the bits of a frame that arrives on the ring immediately, without
waiting for the whole frame to arrive; in other words, frames “flow through” the stations,
56
OMNeT++ Manual – Simple Modules
being delayed only a few bits. If you want to model such networks, the data rate modeling
feature of OMNeT++ cannot be used.
If a message travels along a route, through successive links and compound modules, the
model behaves as if each module waited until the last bit of the message arrives and only
start its transmission then (Fig. 5.2).
Since the above effect is usually not the desired one, typically you will want to assign data
rate to only one connection in the route.
57
OMNeT++ Manual – Simple Modules
While a message is under transmission, other messages have to wait until the transmission
finishes. You can still send messages while the gate is busy, but the beginning of the mod-
elled message transmission will be delayed, just like the gate had an internal queue for the
messages waiting to be transmitted.
The OMNeT++ class library provides you with functions to check whether a certain output
gate is transmitting or to learn when it finishes transmission.
If the connection with a data rate is not the immediate one connected to the simple mod-
ule’s output gate but the second one in the route, you have to check the second gate’s busy
condition.
Message sending is implemented in the following way: the arrival time and the bit error
flag of a message are calculated at once, when the send() (or similar) function is invoked.
That is, if the message travels through several links until it reaches its destination, it is not
scheduled individually for each link, but rather, every calculation is done once, within the
send() call. This implementation was chosen because of its run-time efficiency.
In the actual implementation of queuing the messages at busy gates and modeling the trans-
mission delay, messages do not actually queue up in gates; gates do not have internal queues.
Instead, as the time when each gate will finish transmission is known at the time of sending
the message, the arrival time of the message can be calculated in advance. Then the message
will be stored in the event queue (FES) until the simulation time advances to its arrival time
and it is retrieved by its destination module.
Consequence
The implementation has the following consequence. If you change the delay (or the bit er-
ror rate, or the data rate) of a link during simulation, the modeling of messages sent “just
before” the parameter change will not be accurate. Namely, if link parameters change while
a message is “under way” in the model, that message will not be affected by the parameter
change, although it should. However, all subsequent messages will be modelled correctly.
Similar for data rate: if a data rate changes during the simulation, the change will affect
only the messages that are sent after the change.
If it is important to model gates and channels with changing properties, you can go two ways:
• write sender module such that they schedule events for when the gate finishes its cur-
rent transmission and send then;
• alternatively, you can implement channels with simple modules (“active channels”).
58
OMNeT++ Manual – Simple Modules
Note that some simulators (e.g. OPNET) assign packet queues to input gates (ports), and
messages sent are buffered at the destination module (or the remote end of the link) until
received by the destination module. With that approach, events and messages are separate
entities, that is, a send operation includes placing the message in the packet queue and
scheduling an event which will signal the arrival of the packet. In some implementations,
also output gates have packet queues where packets wait until the channel becomes free
(available for transmission).
OMNeT++ gates don’t have associated queues. The place where the sent but not yet received
messages are buffered is the FES. OMNeT++’s approach is potentially faster than the above
mentioned solution because it doesn’t have the enqueue/dequeue overhead and also spares
an event creation. The drawback is, as mentioned above, that changes to channel parameters
do not take effect immediately.
5.3.1 Overview
The C++ implementation of a simple module consists of:
• declaration of the module class: your class subclassed from cSimpleModule (either
directly or indirectly)
• a module type registration (Define_Module() or Define_Module_Like() macro)
• implementation of the module class
For example, the C++ source for a Sliding Window Protocol implementation might look like
this:
// file: swp.cc
#include <omnetpp.h>
In order to be able to refer to this simple module type in NED files, we should have an
associated NED declaration which might look like this:
59
OMNeT++ Manual – Simple Modules
// file: swp.ned
simple SlidingWindow
parameters:
windowSize: numeric const;
gates:
in: fromNet, fromUser;
out: toNet, toUser;
endsimple
• announces that you’re going to use the class as a simple module type
Define_Module(classname);
Define_Module_Like(classname, neddeclname);
The first form associates the class (subclassed from cSimpleModule) with the NED simple
module declaration of the same name. For example, the
Define_Module(SlidingWindow);
line would ensure that when you create an instance of SlidingWindow in your NED files, the
module has the parameters and gates given in the simple SlidingWindow NED declaration,
and the implementation will be an instance of the SlidingWindow C++ class.
The second form associates the class with a NED simple module declaration of a different
name. You can use this form when you have several modules which share the same interface.
This feature will be discussed in detail in the next section.
Header files
Module declarations should not be put into header files, because they are macros expanding
to lines for which the compiler generates code.
Compound modules
All module types (including compound modules) need to have module declarations. For all
compound modules, the NEDC compiler generates the Define_Module(..) lines automat-
ically. However, it is your responsibility to put Define_Module(..) lines into one of the C++
sources for all your simple module types.
60
OMNeT++ Manual – Simple Modules
Implementation
Unless you are dying to learn about the dirty internals, you may just as well skip this section.
But if you’re interested, here it is: Define_Module() (and also Define_Module_Like())
is a macro which expands to a function definition plus the definition of a global object, some-
thing like this ugly code (luckily, you won’t ever need to be interested in it):
cModuleType MyClass__type("MyClass","MyClass",
(ModuleCreateFunc)MyClass__create);
The cModuleType object can act as a factory: it is able to create an instance of the given
module type. This, together with the fact that all cModuleType objects are available in a
single linked list, allows OMNeT++ to instantiate module types given only their class names
as strings, without having to include the class declaration into any other C++ source.
The global object also stores the name of the NED interface associated with the module class.
The interface description object (another object, generated by nedc) is looked up automati-
cally at network construction time. Whenever a module of the given type is created, it will
automatically have the parameters and gates specified in the associated interface descrip-
tion.
Define_Module_Like(TokenRingMAC, GenericMAC);
Define_Module_Like(EthernetMAC, GenericMAC);
Define_Module_Like(FDDIMAC, GenericMAC);
You won’t be able to directly refer to the TokenRingMAC, EthernetMAC, FDDIMAC module
types in your NED files, because NED doesn’t know about them (their names don’t appear
in any NED file you could import), but you can use them wherever a submodule type was
defined as a parameter to the compound module:
module Host
parameters:
macType: string;
submodules:
mac: macType like GenericMAC;
// if macType=="EthernetMAC" --> OK!
...
endmodule
61
OMNeT++ Manual – Simple Modules
1. either use a macro which expands to the “stock” version of the functions
If you choose the first solution, you use the Module_Class_Members() macro:
The first two arguments are obvious (baseclass is usually cSimpleModule), but stacksize
needs some explanation. If you use activity(), the module code runs as a coroutine, so it
will need a separate stack. (This will be discussed in detail later.)
As an example, the class declaration
If you have data members in the class that you want to initialize in the constructor, you
cannot use the Module_Class_Members() macro. Then you have to write the constructor
yourself.
The constructor should take the following arguments (which you also have to pass further to
the base class):
62
OMNeT++ Manual – Simple Modules
You should not change the number or types of the arguments taken by the constructor, be-
cause it will be called by OMNeT++-generated code.
An example:
If you make a mistake (e.g. you forget to set zero stack size for a handleMessage() simple
module): the default versions of the functions issue error messages telling you what is the
problem.
• will help you understand what it is you’re really programming and bring some structure
into it;
• will enable you to customize the class by inheriting from it and overwriting member
functions
If you have variables which you want to access from all member functions (typically state
variables are like that), you’ll need to add those variables to the class as data members.
Let’s see an example:
63
OMNeT++ Manual – Simple Modules
Define_Module( TransportProtocol );
void TransportProtocol::activity()
{
windowSize = par("windowSize");
n_s = n_r = 0;
eedVector.setName("End-to-End Delay");
eedStats.setName("eedStats");
//...
}
//...
Note that you may have to use the expanded form of the constructor (instead of Mod-
ule_Class_Members()) to pass arguments to the constructors of member objects like eed-
Vector and eedStats. But most often you don’t need to go as far as that; for example, you can
set parameters later from activity(), as shown in the example above.
Define_Module( AdvancedTransportProtocol );
64
OMNeT++ Manual – Simple Modules
void AdvancedTransportProtocol::recalculateTimeout()
{
//...
}
If possible, avoid them. Do not use global variables, including static class members. There
are several problems with them.
First, they are not reset to their initial values (to zero) when you rebuild the simulation in
Tkenv, or start another run in Cmdenv. This may produce surprising results.
Second, they prevent you from running your simulation in parallel. When using parallel
simulation, each partition of your model (may) run in a separate process, having its own
copy of the global variables. This is usually not what you want.
5.4.1 activity()
Process-style description
With activity(), you can code the simple module much like you would code an operating
system process or a thread. You can wait for an incoming message (event) at any point of the
code, you can suspend the execution for some time (model time!), etc. When the activity()
function exits, the module is terminated. (The simulation can continue if there are other
modules which can run.)
The most important functions you can use in activity() are (they will be discussed in
detail later):
• end() – to finish execution of this module (same as exiting the activity() function)
The activity() function normally contains an infinite loop, with at least a wait() or
receive() call in its body.
65
OMNeT++ Manual – Simple Modules
Application area
One area where the process-style description is especially convenient is when the process has
many states but transitions are very limited, ie. from any state the process can only go to one
or two other states. For example, this is the case when programming a network application
which uses a single network connection. The pseudocode of the application which talks to a
transport layer protocol might look like this:
activity()
{
while(true)
{
open connection by sending OPEN command to transport layer
receive reply from transport layer
if (open not successful)
{
wait(some time)
continue // loop back to while()
}
If you want to handle several connections simultaneously, you may dynamically create as
instances of the simple module above as needed. Dynamic module creation will be discussed
later.
Activity() is run in a coroutine. Coroutines are a sort of threads which are scheduled
non-preemptively (this is also called cooperative multitasking). From one coroutine you can
switch to another coroutine by a transferTo(otherCoroutine) call. Then this corou-
66
OMNeT++ Manual – Simple Modules
tine is suspended and otherCoroutine will run. Later, when otherCoroutine does a trans-
ferTo(firstCoroutine) call, execution of the first coroutine will resume from the point
of the transferTo(otherCoroutine) call. The full state of the coroutine, including local
variables are preserved while the thread of execution is in another coroutines. This implies
that each coroutine must have an own processor stack, and transferTo() involves a switch
from one processor stack to another.
Coroutines are at the heart of OMNeT++, and the simulation programmer doesn’t ever need
to call transferTo() or other functions in the coroutine library, nor does he need to care
about the coroutine library implementation. But it is important to understand how the event
loop found in discrete event simulators works with coroutines.
When using coroutines, the event loop looks like this (simplified):
That is, when the module has an event, the simulation kernel transfers the control to the
module’s coroutine. It is expected that when the module “decides it has finished the process-
ing of the event”, it will transfer the control back to the simulation kernel by a trans-
ferTo(main) call. Initially, simple modules using activity() are “booted” by events
(”starter messages”) inserted into the FES by the simulation kernel before the start of the
simulation.
How does the coroutine know it has “finished processing the event”? The answer: when it
requests another event. The functions which request events from the simulation kernel are
the receive() and wait(), so their implementations contain a transferTo(main) call
somewhere.
Their pseudocode, as implemented in OMNeT++:
receive()
{
transferTo(main)
retrieve current event
return the event // remember: events = messages
}
wait()
{
create event e
schedule it at (current sim. time + wait interval)
transferTo(main)
retrieve current event
if (current event is not e) {
error
}
delete e // note: actual impl. reuses events
return
}
Thus, the receive() and wait() calls are special points in the activity() function, be-
cause that’s where:
67
OMNeT++ Manual – Simple Modules
Starter messages
Modules written with activity() need starter messages to “boot”. These starter messages
are inserted into the FES automatically by OMNeT++ at the beginning of the simulation,
even before the initialize() functions are called.
All the simulation programmer needs to care about coroutines is to choose the processor stack
size for them. This cannot be automated (Eerrr... at least not without hardware support,
some trick with virtual memory handling).
16 kbytes is usually a good choice, but you may need more if the module uses recursive
functions or has local variables which occupy a lot of stack space. OMNeT++ has a built-
mechanism that will usually detect if the module stack is too small and overflows. OMNeT++
can also tell you how much stack space a module actually uses, so you can find it out if you
overestimated the stack needs.
Because local variables of activity() are preserved across events, you can store everything
(state information, packet buffers, etc.) in them. Local variables can be initialized at the top
of the activity() function, so there isn’t much need to use initialize().
However, you need finish() if you want to write statistics at the end of the simulation.
And because finish() cannot access the local variables of activity(), you have to put the
variables and objects that contain the statistics into the module class. You still don’t need
initialize() because class members can also be initialized at the top of activity().
Thus, a typical setup looks like this pseudocode:
class MySimpleModule...
{
...
variables for statistics collection
activity();
finish();
};
MySimpleModule::activity()
{
declare local vars and initialize them
initialize statistics collection variables
while(true)
{
...
}
}
68
OMNeT++ Manual – Simple Modules
MySimpleModule::finish()
{
record statistics into file
}
Advantages:
Drawbacks:
• memory overhead: stack allocation may unacceptably increase the memory require-
ments of the simulation program if you have several thousands or ten thousands of
simple modules;
Other simulators
• All simulation software which inherit from SIMULA (e.g. C++SIM) are based on corou-
tines, although all in all the programming model is quite different.
• The simulation/parallel programming language Maisie and its successor PARSEC (from
UCLA) also use coroutines (although implemented on with “normal” preemptive threads).
The philosophy is quite similar to OMNeT++. PARSEC, being “just” a programming
language, has a more elegant syntax but much less features than OMNeT++.
5.4.2 handleMessage()
Function called for each event
The idea is that at each event we simply call a user-defined function instead of switching
to a coroutine that has activity() running in it. The “user-defined function” is the han-
dleMessage(cMessage *msg) virtual member function of cSimpleModule; the user has
to redefine the function to make it do useful work. Calls to handleMessage() occur in the
main stack of the program – no coroutine stack is needed and no context switch is done.
The 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 simula-
tion time is potentially different in each call. No simulation time elapses within a call to
handleMessage().
The pseudocode of the event loop which is able to handle both activity() and handleMes-
sage() simple modules:
69
OMNeT++ Manual – Simple Modules
Modules with handleMessage() are NOT started automatically: the simulation kernel cre-
ates starter messages only for modules with activity(). This means that you have to
schedule self-messages from the initialize() function if you want a handleMessage()
simple module to start working “by itself ”, without first receiving a message from other mod-
ules.
To use the handleMessage() mechanism in a simple module, you must specify zero stack
size for the module. This is important, because this tells OMNeT++ that you want to use
handleMessage() and not activity().
Message/event related functions you can use in handleMessage():
You cannot use the receive() family and wait() functions in handleMessage(), because
they are coroutine-based by nature, as explained in the section about 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 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:
You can initialize these variables from the initialize() function. The constructor is not
a very good place for this purpose, because it is called in the network setup phase when
the model is still under construction, so a lot of information you may want to use is not yet
available then.
70
OMNeT++ Manual – Simple Modules
Another task you have to do in initialize() is to schedule initial event(s) which trigger
the first call(s) to handleMessage(). After the first call, 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.
finish() is used in the normal way: to record statistics information accumulated in data
members of the class at the end of the simulation.
Application area
1. When you expect the module to be used in large simulations, involving several thou-
sand modules. In such cases, the module stacks required by activity() would simply
consume too much memory.
2. For modules which maintain little or no state information, such as packet sinks, han-
dleMessage() is more convenient to program.
3. 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 activity(), or the result is
code which is better suited for handleMessage() (see rule of thumb below). Most
communication protocols are like this.
In general, if your activity() function contains no wait() and it has only one receive()
call at the top of an infinite loop (while(true) or for(;;)), you can trivially convert it to
handleMessage(). The body of the infinite loop becomes the body to handleMessage(),
state variables inside activity() become data members in the module class, and you ini-
tialize them in initialize().
That is, the following code:
activity()
{
initialization code
while(true)
{
msg = receive();
// code which doesn’t contain
// receive() or wait() calls
}
}
initialize()
{
initialization code
}
handleMessage( msg )
{
// code which doesn’t contain
// receive() or wait() calls
}
71
OMNeT++ Manual – Simple Modules
The code for simple packet generators and sinks programmed with handleMessage() might
be as simple as this:
PacketGenerator::handleMessage(m)
{
create and send out packet
schedule m again to trigger next call to handleMessage
// (self-message)
}
PacketSink::handleMessage(m)
{
delete m
}
Note that PacketGenerator will need to redefine initialize() to create 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.)
Define_Module( Generator );
void Generator::initialize()
{
// schedule first sending
scheduleAt(simTime(), new cMessage);
}
A bit more realistic example is to rewrite our Generator to create packet bursts, each con-
sisting of burstLength packets.
We add some data members to the class:
72
OMNeT++ Manual – Simple Modules
• burstLength will store the parameter that specifies how many packets a burst must
contain,
• burstCounter will count in how many packets are left to be sent in the current burst.
The code:
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);
}
Advantages:
73
OMNeT++ Manual – Simple Modules
Drawbacks:
Other simulators
Many simulation packages use a similar approach, often topped with something like a state
machine (FSM) which hides the underlying function calls. Such systems are:
• OPNET(T M ) (MIL3, Inc.) which uses FSM’s designed using a graphical editor;
The initialize() functions of the modules are invoked before the first event is processed,
but after the initial events (starter messages) have been placed into the FES by the simula-
tion kernel.
Both simple and compound modules have initialize() functions. A compound module
has its initialize() function called before all its submodules have.
The finish() functions are called when the event loop has terminated, and only if it ter-
minated normally (i.e. not with a runtime error). The calling order is the reverse as with
initialize(): first submodules, then the containing compound module. (The bottom line is
that in the moment there’s no “official” possibility to redefine initialize() and finish()
for compound modules; the unofficial way is to write into the nedc-generated C++ code. Fu-
ture versions of OMNeT++ will support adding these functions to compound modules.)
This is summarized in the following pseudocode (although you won’t find this code “as is” in
the simulation kernel sources):
74
OMNeT++ Manual – Simple Modules
callInitialize()
{
call to user-defined initialize() function
if (module is compound)
for (each submodule)
do callInitialize() on submodule
}
callFinish()
{
if (module is compound)
for (each submodule)
do callFinish() on submodule
call to user-defined finish() function
}
Usually you should not put simulation-related code into the simple module constructor. For
example, modules often need to investigate their surroundings (maybe the whole network)
at the beginning of the simulation and save the collected info into internal tables. Code like
that cannot be placed into the constructor since the network is still being set up when the
constructor is called.
Keep in mind that finish() is not always called, so it isn’t a good place for cleanup code
which should run every time the module is deleted. finish() is only a good place for writing
statistics, result post-processing and other stuff which are to run only on successful comple-
tion.
Cleanup code should go into the destructor. But in fact, you almost never need to write a
destructor because OMNeT++ keeps track of objects you create and disposes of them auto-
matically (sort of automatic garbage collection). However it cannot track objects not derived
from cObject, so they may need to be deleted manually from the destructor. Garbage col-
lection is discussed in more detail in section 7.13.5.
Multi-stage initialization
75
OMNeT++ Manual – Simple Modules
At the beginning of the simulation, initialize(0) is called for all modules, then ini-
tialize(1), initialize(2), etc. You can think of it like initialization takes place in
several “waves”. For each module, numInitStages() must be redefined to return the num-
ber of init stages required, e.g. for a two-stage init, numInitStages() should return 2, and
initialize(int stage) must be implemented to handle the stage=0 and stage=1 cases. 1
The callInitialize() function performs the full multi-stage initialization for that module
and all its submodules.
If you do not redefine the multi-stage initialization functions, the default behavior is single-
stage initialization: the default numInitStages() returns 1, and the default initial-
ize(int stage) simply calls initialize().
“End-of-Simulation” event
The task of finish() is solved in many simulators (e.g. OPNET) by introducing a special
end-of-simulation event. This is not a very good practice because the simulation programmer
has to code the models (often represented as FSMs) so that they can always properly respond
to end-of-simulation events, in whichever state they are. This often makes program code
unnecessarily complicated.
This fact is also evidenced in the design of the PARSEC simulation language (UCLA). Its pre-
decessor Maisie used end-of-simulation events, but – as documented in the PARSEC manual
– this has led to awkward programming in many cases, so for PARSEC end-of-simulation
events were dropped in favour of finish() (called finalize() in PARSEC).
Finite State Machines (FSMs) can make life with handleMessage() easier. OMNeT++ pro-
vides a class and a set of macros to build FSMs. OMNeT++’s FSMs work very much like
OPNET’s or SDL’s.
The key points are:
• There are two kinds of states: transient and steady. At each event (that is, at each call
to handleMessage()), the FSM transitions out of the current (steady) state, undergoes
a series of state changes (runs through a number of transient states), and finally arrives
at another steady state. Thus between two events, the system is always in one of the
steady states. Transient states are therefore not really a must – they exist only to group
actions to be taken during a transition in a convenient way.
• You can assign program code to entering and leaving a state (known as entry/exit code).
Staying in the same state is handled as leaving and re-entering the state.
• Entry code should not modify the state (this is verified by OMNeT++). State changes
(transitions) must be put into the exit code.
OMNeT++’s FSMs can be nested. This means that any state (or rather, its entry or exit code)
may contain a further full-fledged FSM_Switch() (see below). This allows you to introduce
sub-states and thereby bring some structure into the state space if it would become too large.
1 Note const in the numInitStages() declaration. If you forget it, by C++ rules you create a different function
instead of redefining the existing one in the base class, thus the existing one will remain if effect and return 1.
76
OMNeT++ Manual – Simple Modules
FSM state is stored in an object of type cFSM. The possible states are defined by an enum;
the enum is also a place to tell which state is transient and which is steady. In the following
example, SLEEP and ACTIVE are steady states and SEND is transient (the numbers in
parens must be unique within the state type and they are used for constructing the numeric
IDs for the states):
enum {
INIT = 0,
SLEEP = FSM_Steady(1),
ACTIVE = FSM_Steady(2),
SEND = FSM_Transient(1),
};
The actual FSM is embedded in a switch-like statement, FSM_Switch(), where you have
cases for entering and leaving each state:
FSM_Switch(fsm)
{
case FSM_Exit(state1):
//...
break;
case FSM_Enter(state1):
//...
break;
case FSM_Exit(state2):
//...
break;
case FSM_Enter(state2):
//...
break;
//...
};
State transitions are done via calls to FSM_Goto(), which simply stores the new state in the
cFSM object:
FSM_Goto(fsm,newState);
The FSM starts from the state with the numeric code 0; this state is conventionally named
INIT.
Debugging FSMs
FSMs can log their state transitions ev, with the output looking like this:
...
FSM GenState: leaving state SLEEP
FSM GenState: entering state ACTIVE
...
FSM GenState: leaving state ACTIVE
77
OMNeT++ Manual – Simple Modules
To enable the above output, you have to #define FSM_DEBUG before including omnetpp.h.
The actual logging is done via the FSM_Print() macro. It is currently defined as follows, but
you can change the output format by undefining FSM_Print() after including omnetpp.ini
and providing a new definition instead.
#define FSM_Print(fsm,exiting)
(ev << "FSM " << (fsm).name()
<< ((exiting) ? ": leaving state " : ": entering state ")
<< (fsm).stateName() << endl)
Implementation
An example
Let us write another flavour of a bursty generator. It has two states, SLEEP and ACTIVE.
In the SLEEP state, the module does nothing. In the ACTIVE state, it sends messages with
a given inter-arrival time. The code was taken from the Fifo2 sample simulation.
#define FSM_DEBUG
#include <omnetpp.h>
// parameters
double sleepTimeMean;
double burstTimeMean;
double sendIATime;
cPar *msgLength;
78
OMNeT++ Manual – Simple Modules
enum {
INIT = 0,
SLEEP = FSM_Steady(1),
ACTIVE = FSM_Steady(2),
SEND = FSM_Transient(1),
};
// variables used
int i;
cMessage *startStopBurst;
cMessage *sendMessage;
Define_Module( BurstyGenerator );
void BurstyGenerator::initialize()
{
fsm.setName("fsm");
sleepTimeMean = par("sleep_time_mean");
burstTimeMean = par("burst_time_mean");
sendIATime = par("send_ia_time");
msgLength = &par("msg_length");
i = 0;
WATCH(i); // always put watches in initialize()
startStopBurst = new cMessage("startStopBurst");
sendMessage = new cMessage("sendMessage");
scheduleAt(0.0,startStopBurst);
}
79
OMNeT++ Manual – Simple Modules
FSM_Goto(fsm,ACTIVE);
break;
case FSM_Enter(ACTIVE):
// schedule next sending
scheduleAt(simTime()+exponential(sendIATime), sendMessage);
break;
case FSM_Exit(ACTIVE):
// transition to either SEND or SLEEP
if (msg==sendMessage) {
FSM_Goto(fsm,SEND);
} else if (msg==startStopBurst) {
cancelEvent(sendMessage);
FSM_Goto(fsm,SLEEP);
} else {
error("invalid event in state ACTIVE");
}
break;
case FSM_Exit(SEND):
{
// generate and send out job
char msgname[32];
sprintf( msgname, "job-%d", ++i);
ev << "Generating " << msgname << endl;
cMessage *job = new cMessage(msgname);
job->setLength( (long) *msgLength );
job->setTimestamp();
send( job, "out" );
// return to ACTIVE
FSM_Goto(fsm,ACTIVE);
break;
}
}
}
On an abstract level, an OMNeT++ simulation model is a set of simple modules that com-
municate with each other via message passing. The essence of simple modules is that they
create, send, receive, store, modify, schedule and destroy messages – everything else is sup-
posed to facilitate this task, and collect statistics about what was going on.
Messages in OMNeT++ are instances of the cMessage class or one of its subclasses. Message
objects are created using the C++ new operator and destroyed using the delete operator
when they are no longer needed. During their lifetimes, messages travel between modules
via gates and connections (or are sent directly, bypassing the connections), or they are sched-
uled by and delivered to modules, representing internal events of that module.
Messages are described in detail in chapter 6. At this point, all we need to know about
them is that they are referred to as cMessage * pointers. Message objects can be given
descriptive names (a const char * string) that often helps in debugging the simulation.
The message name string can be specified in the constructor, so it should not surprise you if
you see something like new cMessage("token") in the examples below.
80
OMNeT++ Manual – Simple Modules
In the first function, the argument gateName is the name of the gate the message has to be
sent through. If this gate is a vector gate, index determines though which particular output
gate this has to be done; otherwise, the index argument is not needed.
The second function uses the gate Id, and because it does not have to search through the gate
array, it is faster than the first one.
Examples:
send(msg, "outGate");
send(msg, "outGates", i); // send via outGates[i]
The following code example creates and sends messages every 5 simulated seconds:
If you’re sending messages over a link that has (nonzero) data rate, it is modeled in the way
that has been described earlier in this manual, in section 5.2.
If you want to have full control over the transmission process, you’ll probably need the is-
Busy() and transmissionFinishes() member functions of cGate. They are described in
section 5.8.3.
Broadcasting messages
In your model, you may need to broadcast a message to several destinations. Broadcast can
be implemented in a simple module by sending out copies of the same message, for example
on every gate of a gate vector. As described above, you cannot use the same message pointer
for in all send() calls – what you have to do instead is create copies (duplicates) of the
message object and send them.
Example:
81
OMNeT++ Manual – Simple Modules
You might have noticed that copying the message for the last gate is redundant (we could
send out the original message), so it can be optimized out like this:
Retransmissions
// (re)transmit packet:
cMessage *copy = (cMessage *) packet->dup();
send(copy, "out");
delete packet;
Why?
A message is like any real world object – it cannot be at two places at the same time. Once
you’ve sent it, the message object no longer belongs to the module: it is taken over by the
simulation kernel, and will eventually be delivered to the destination module. The sender
module should not even refer to its pointer any more. Once the message arrived in the
destination module, that module will has full authority over it – it can send it further, destroy
it immediately, or store it for further handling. The same applies to messages that have been
scheduled – they belong to the simulation kernel until they are delivered back to the module.
To enforce the rules above, all message sending functions check that you actually own the
message you are about to send. If the message is with another module, it is currently sched-
uled or in a queue etc., you’ll get a runtime error: not owner of message. 2
2 The feature does not increase runtime overhead significantly, because it uses the object ownership management
(described in Section 7.13); it merely checks that the owner of the message is the module that wants to send it.
82
OMNeT++ Manual – Simple Modules
wait( some_delay );
send( msg, "outgate" );
If the module needs to react to messages that arrive during the delay, wait() cannot be
used and the timer mechanism described in Section 5.6.7, “Self-messages”, would need to be
employed.
However, there is a more straightforward method than the above two, and this is delayed
sending. Delayed sending can be done with one of these functions:
sendDelayed(cMessage *msg, double delay, const char *gate_name, int in-
dex);
sendDelayed(cMessage *msg, double delay, int gate_id);
The arguments are the same as for send(), except for the extra delay parameter. The effect
of the function is the same as if the module had kept the message for the delay interval and
sent it afterwards. That is, the sending time of the message will be the current simulation
time (time at the sendDelayed() call) plus the delay. The delay value must be nonnegative.
Example:
The receive() function accepts an optional timeout parameter. (This is a delta, not an ab-
solute simulation time.) If an appropriate message doesn’t arrive within the timeout period,
the function returns a NULL pointer.
83
OMNeT++ Manual – Simple Modules
if (msg==NULL)
{
... // handle timeout
}
else
{
... // process message
}
Earlier versions of OMNeT++ supported something called putaside-queue which stored mes-
sages that arrived while the module was waiting for something else. For example, a function
called receiveOn() waited for a message to arrive on a specific gate, and messages that
actually arrived on another gate were inserted in the putaside-queue. receive() returned
messages from the putaside-queue first, and it waited for new messages to arrive only if
putaside-queue was empty.
Practice has shown that putaside-queue and associated functionality bore little usefulness,
and at the same time it was very confusing to people. Therefore putaside-queue, receiveOn(),
receiveNew(), and receiveNewOn() have been deprecated in OMNeT++ 2.3, and will be
entirely removed from further releases.
wait( delay );
In other simulation software, wait() is often called hold. Internally, the wait() function
is implemented by a scheduleAt() followed by a receive(). The wait() function is very
convenient in modules that do not need to be prepared for arriving messages, for example
message generators. An example:
for(;;)
{
// wait for a (potentially random amount of) time, specified
// in the interArrivalTime module parameter
wait( par("interArrivalTime") );
If you expect messages to arrive during the wait period, you can use the waitAndEnqueue()
function. It takes a pointer to a queue object (of class cQueue, described in chapter 7) in addi-
tion to the wait interval. Messages that arrive during the wait interval will be accumulated
in the queue, so you can process them after the wait() call has returned.
84
OMNeT++ Manual – Simple Modules
cQueue queue("queue");
...
waitAndEnqueue(waitTime, &queue);
if (!queue.empty())
{
// process messages arrived during wait interval
...
}
The semantics of wait() has slightly changed after OMNeT++ 2.3, due to the deprecation
of the putaside-queue (see section 5.6.5). Up to release 2.3, messages that arrived during the
wait interval were accumulated in the putaside-queue. In later releases, it will be a runtime
error if a message arrives during the wait interval – if you want to allow such messages, you
should use waitAndEnqueue().
Scheduling an event
The module can send a message to itself using the scheduleAt() function. scheduleAt()
accepts an absolute simulation time, usually calculated as simTime()+delta:
scheduleAt(absoluteTime, msg);
scheduleAt(simtime()+delta, msg);
Self-messages are delivered to the module in the same way as other messages (via the usual
receive calls or handleMessage()); the module may call the isSelfMessage() member of
any received message to determine if it is a self-message.
As an example, here’s how you could implement your own wait() function in an activ-
ity() simple module, if the simulation kernel didn’t provide it already:
85
OMNeT++ Manual – Simple Modules
You can determine if a message is currently in the FES by calling its isScheduled() mem-
ber:
if (msg->isScheduled())
// currently scheduled
else
// not scheduled
Re-scheduling an event
Cancelling an event
Scheduled self-messages can be cancelled (removed from the FES). This is particularly useful
because self-messages are often used to model timers.
cancelEvent( msg );
The cancelEvent() function takes a pointer to the message to be cancelled, and also re-
turns the same pointer. After having it cancelled, you may delete the message or reuse it in
the next scheduleAt() calls. cancelEvent() gives an error if the message is not in the
FES.
Implementing timers
scheduleAt(simTime()+10.0, timeoutEvent);
//...
86
OMNeT++ Manual – Simple Modules
However, typically you don’t need endSimulation() because you can specify simulation
time and CPU time limits in the ini file (see later).
Stopping on errors
If your simulation detects an error condition and wants to stop the simulation, you can do it
with the error() member function of cModule. It is used like printf():
if (windowSize<1)
error("Invalid window size %d; must be >=1", windowSize);
Do not include a newline (“\n”) or punctuation (period or exclamation mark) in the error text,
as it will be added by OMNeT++.
The cPar class is a general value-storing object. It supports type casts to numeric types, so
parameter values can be read like this:
If the parameter is a random variable or its value can change during execution, it is best to
store a reference to it and re-read the value each time it is needed:
If the wait_time parameter was given a random value (e.g. exponential(1.0)) in the NED
source or the ini file, the above code results in a different delay each time.
Parameter values can also be changed from the program, during execution. If the param-
eter was taken by reference (with a ref modifier in the NED file), other modules will also
see the change. Thus, parameters taken by reference can be used as a means of module
communication.
An example:
par("wait_time") = 0.12;
Or:
See cPar explanation later in this manual for further information on how to change a cPar’s
value.
87
OMNeT++ Manual – Simple Modules
For gate vectors, the first form returns the first gate in the vector (at index 0).
The isVector() member function can be used to determine if a gate belongs to a gate vector
or not. But this is almost insignificant, because non-vector gates are treated as vectors with
size 1.
Given a gate pointer, you can use the size() and index() member functions of cGate to
determine the size of the gate vector and the index of the gate within the vector:
Gate IDs
Module gates (input and output, single and vector) are stored in an array within their mod-
ules. The gate’s position in the array is called the gate ID. The gate ID is returned by the
id() member function:
int id = outgate->id();
For a module with input gates from_app and in[3] and output gates of to_app and status, the
array may look like this:
ID dir name[index]
0 input from_app
1 output to_app
2 empty
3 input in[0]
4 input in[1]
5 input in[2]
6 output status
The array may have empty slots. Gate vectors are guaranteed to occupy contiguous IDs, thus
it is legal to calculate the ID of gate[k] as gate("gate",0).id()+k.
88
OMNeT++ Manual – Simple Modules
Message sending and receiving functions accept both gate names and gate IDs; the functions
using gate IDs are a bit faster. Gate IDs do not change during execution, so it is often worth
retrieving them in advance and using them instead of gate names.
You can also obtain gate IDs with the findGate() member of cModule:
If the connection with a data rate is not immediately the one connected to the simple mod-
ule’s output gate but the second one in the route, you have to check the second gate’s busy
condition. You would use the following code:
if (gate("mygate")->toGate()->isBusy())
//...
Note that if data rates change during the simulation, the changes will affect only the mes-
sages that are sent after the change.
5.8.4 Connectivity
The isConnected() member function returns whether the gate is connected. If the gate
is an output gate, the gate to which it is connected is obtained by the toGate() member
function. For input gates, the function is fromGate().
89
OMNeT++ Manual – Simple Modules
To find out to which simple module a given output gate leads finally, you would have to walk
along the path like this (the ownerModule() member function returns the module to which
the gate belongs):
but luckily, there are two convenience functions which do that: sourceGate() and desti-
nationGate().
If a module is part of a module vector, the index() and size() member functions can be
used to query its index and the vector size:
Module IDs
Each module in the network has a unique ID that is returned by the id() member function.
The module ID is used internally by the simulation kernel to identify modules.
90
OMNeT++ Manual – Simple Modules
If you know the module ID, you can ask the simulation object (a global variable) to get back
the module pointer:
int id = 100;
cModule *mod = simulation.module( id );
Module IDs are guaranteed to be unique, even when modules are created and destroyed
dynamically. That is, an ID which once belonged to a module which was deleted is never
issued to another module later.
The surrounding compound module can be accessed by the parentModule() member func-
tion:
For example, the parameters of the parent module are accessed like this:
compoundmod->moduleByRelativePath("child[5].grandchild");
compoundmod->submodule("child",5)->submodule("grandchild");
(Provided that child[5] does exist, because otherwise the second version will crash with an
access violation because of the NULL pointer.)
The cSimulation::moduleByPath() function is similar to cModule’s moduleByRelative-
Path() function, and it starts the search at the top-level module.
91
OMNeT++ Manual – Simple Modules
To determine the module at the other end of a connection, use cGate’s fromGate(), to-
Gate() and ownerModule() functions. For example:
5.10.2 Overview
To understand how dynamic module creation works, you have to know a bit about how nor-
mally OMNeT++ instantiates modules. Each module type (class) has a corresponding fac-
tory object of the class cModuleType. This object is created under the hood by the De-
92
OMNeT++ Manual – Simple Modules
fine_Module() macro, and it has a factory function which can instantiate the module class
(this function basically only consists of a return new module-class(...) statement).
The cModuleType object can be looked up by its name string (which is the same as the
module class name). Once you have its pointer, it’s possible to call its factory method and
create an instance of the corresponding module class – without having to include the C++
header file containing module’s class declaration into your source file.
The cModuleType object also knows what gates and parameters the given module type has
to have. (This info comes from compiled NED code.)
Simple modules can be created in one step. For a compound module, the situation is more
complicated, because its internal structure (submodules, connections) may depend on pa-
rameter values and gate vector sizes. Thus, for compound modules it is generally required
to first create the module itself, second, set parameter values and gate vector sizes, and then
call the method that creates its submodules and internal connections.
As you know already, simple modules with activity() need a starter message. For stati-
cally created modules, this message is created automatically by OMNeT++, but for dynami-
cally created modules, you have to do this explicitly by calling the appropriate functions.
Calling initialize() has to take place after insertion of the starter messages, because
the initializing code may insert new messages into the FES, and these messages should be
processed after the starter message.
Simplified form
mod = modtype->createScheduleInit("name",this);
Expanded form
2. create module
93
OMNeT++ Manual – Simple Modules
4. call function that builds out submodules and finalizes the module
5. call function that creates activation message(s) for the new simple module(s)
Each step (except for Step 3.) can be done with one line of code.
See the following example, where Step 3 is omitted:
// create (possibly compound) module and build its submodules (if any)
cModule *module = moduleType->create( "TCPconn", this );
module->buildInside();
If you want to set up parameter values or gate vector sizes (Step 3.), the code goes between
the create() and buildInside() calls:
module->deleteModule();
If the module was a compound module, this involves recursively destroying all its submod-
ules. A simple module can also delete itself; in this case, if the module was implemented
using activity(), the deleteModule() call does not return to the caller (the reason is
that deleting the module also deletes the CPU stack of the coroutine).
Currently, you cannot safely delete a compound module from a simple module in it; you must
delegate the job to a module outside the compound module.
srcGate->connectTo(destGate);
3 The earlier connect() global functions that accepted two gates have been deprecated, and may be removed
94
OMNeT++ Manual – Simple Modules
The source and destination words correspond to the direction of the arrow in NED files and
which is the same, the direction of messages. That is, you connect an output gate to another
submodule’s input gate; you connect a submodule’s output gate to the output gate of its
parent module; and an input gate of a compound module to the input gate of its submodule.
As an example, we create two modules and connect them in both directions:
a->gate("out")->connectTo(b->gate("in"));
b->gate("out")->connectTo(a->gate("in"));
95
OMNeT++ Manual – Simple Modules
96
OMNeT++ Manual – Messages
Chapter 6
Messages
Attributes
A cMessage object has number of attributes. Some are used by the simulation kernel, others
are provided just for the convenience of the simulation programmer. A more-or-less complete
list:
• The name attribute is a string (const char *), which can be freely used by the simu-
lation programmer. The message name appears in many places in Tkenv (for example,
in animations), and it is generally very useful to choose a descriptive name. This at-
tribute is inherited from cObject (see section 7.1).
• The message kind attribute is supposed to carry some message type information. Zero
and positive values can be freely used for any purpose. Negative values are reserved
for use by the OMNeT++ simulation library.
• The length attribute (understood in bits) is used to compute transmission delay when
the message travels through a connection that has an assigned data rate.
• The bit error flag attribute is set to true by the simulation kernel with a probability of
1 − (1 − ber)length when the message is sent through a connection that has an assigned
bit error rate (ber).
• The priority attribute is used by the simulation kernel to order messages in the message
queue (FES) that have the same arrival time values.
• The time stamp attribute is not used by the simulation kernel; you can use it for pur-
poses like remembering the time when the message was enqueued or re-sent.
• Other attributes and data members make simulation programming easier, they will be
discussed later: parameter list, encapsulated message, context pointer.
97
OMNeT++ Manual – Messages
• A number of read-only attributes store information about the message’s (last) send-
ing/scheduling: source/destination module and gate, sending (scheduling) and arrival
time. They are mostly used by the simulation kernel while the message is in the FES,
but the information is still in the message object when a module receives the message.
Basic usage
The cMessage constructor accepts several arguments. Most commonly, you would create a
message using an object name (a const char * string) and a message kind (int):
Both arguments are optional and initialize to the null string ("") and 0, so the following
statements are also valid:
It is a good idea to always use message names – they can be extremely useful when debugging
or demonstrating your simulation.
Message kind is usually initialized with a symbolic constant (e.g. an enum value) which
signals what the message object represents in the simulation (i.e. a data packet, a jam
signal, a job, etc.) Please use positive values or zero only as message kind – negative values
are reserved for use by the simulation kernel.
The cMessage constructor accepts further arguments too (length, priority, bit error flag),
but for readability of the code it is best to set them explicitly via the set...() methods
described below. Length and priority are integers, and the bit error flag is boolean.
Once a message has been created, its data members can be changed by the following func-
tions:
msg->setKind( kind );
msg->setLength( length );
msg->setPriority( priority );
msg->setBitError( err );
msg->setTimestamp();
msg->setTimestamp( simtime );
With these functions the user can set the message kind, the message length, the priority, the
error flag and the time stamp. The setTimeStamp() function without any argument sets
the time stamp to the current simulation time.
The values can be obtained by the following functions:
int k = msg->kind();
int p = msg->priority();
int l = msg->length();
bool b = msg->hasBitError();
simtime_t t = msg->timestamp();
Duplicating messages
It is often needed to duplicate a message (for example, send one and keep a copy). This can
be done in the standard ways as for any other OMNeT++ object:
98
OMNeT++ Manual – Messages
The two are equivalent. The resulting message is an exact copy of the original, including
message parameters (cPar or other object types) and encapsulated messages.
userdata->setLength(8*2000);
cMessage *tcpseg = new cMessage("tcp");
tcpseg->setLength(8*24);
tcpseg->encapsulate(userdata);
ev << tcpseg->length() << endl; // --> 8*2024 = 16192
A message can only hold one encapsulated message at a time. The second encapsulate()
call will result in an error. It is also an error if the message to be encapsulated isn’t owned
by the module.
You can get back the encapsulated message by decapsulate():
decapsulate() will decrease the length of the message accordingly, except if it was zero. If
the length would become negative, an error occurs.
The encapsulatedMsg() function returns a pointer to the encapsulated message, or NULL
if no message was encapsulated.
bool isSelfMessage()
The following methods can tell where the message came from and where it arrived (will
arrive).
int senderModuleId();
int senderGateId();
int arrivalModuleId();
int arrivalGateId();
99
OMNeT++ Manual – Messages
The following two methods are just convenience functions that combine module id and gate
id into a gate object pointer.
cGate *senderGate();
cGate *arrivalGate();
And there are further convenience functions to tell whether the message arrived on a specific
gate given with id or name+index.
The following methods return message creation time and the last sending and arrival times.
simtime_t creationTime()
simtime_t sendingTime();
simtime_t arrivalTime();
It can be used for any purpose by the simulation programmer. It is not used by the simulation
kernel, and it is treated as a mere pointer (no memory management is done on it).
Intended purpose: a module which schedules several self-messages (timers) will need to
identify a self-message when it arrives back to the module, ie. the module will have to
determine which timer went off and what to do then. The context pointer can be made to
point at a data structure kept by the module which can carry enough “context” information
about the event.
telecommunications network. cPacket added two data members: protocol and PDU (Protocol Data Unit, an OSI
term). It was envisioned that procotol and PDU would take their values from globally maintained enums. As it
turned out, this was utopistic. In practice, usage of protocol was inconsistent (protocol and message kind played
similar roles, creating confusion), and models did not use PDU at all. Hence the deprecation of cPacket.
100
OMNeT++ Manual – Messages
Attaching objects
The cMessage class has an internal cArray object which can carry objects. Only objects
that are derived from cObject (most OMNeT++ classes are so) can be attached. The addOb-
ject(), getObject(), hasObject(), removeObject() methods use the object name as
the key to the array. An example:
You should take care that names of the attached objects do not clash with each other or with
cPar parameter names (see next section). If you do not attach anything to the message and
do not call the parList() function, the internal cArray object will not be created. This
saves both storage and execution time.
You can attach non-object types (or non-cObject objects) to the message by using cPar’s
void* pointer ’P’) type (see later in the description of cPar). An example:
Attaching parameters
The preferred way of extending messages with new data fields is to use message definitions
(see section 6.2).
The old, deprecated way of adding new fields to messages is via attaching cPar objects. There
are several downsides of this approach, the worst being being large memory and execution
time overhead. cPar’s are heavy-weight and fairly complex objects themselves. It has been
reported that using cPar message parameters might account for a large part of execution
time, sometimes as much as 80%. Using cPars is also error-prone because cPar objects have
to be added dynamically and individually to each message object. In contrast, subclassing
101
OMNeT++ Manual – Messages
benefits from static type checking: if you mistype the name of a field in the C++ code, already
the compiler can detect the mistake.
However, if you still need to use cPars, here’s a short summary how you can do it. You
add a new parameter to the message with the addPar() member function, and get back a
reference to the parameter object with the par() member function. hasPar() tells you if the
message has a given parameter or not. Message parameters can be accessed also by index in
the parameter array. The findPar() function returns the index of a parameter or -1 if the
parameter cannot be found. The parameter can then be accessed using an overloaded par()
function.
Example:
msg->addPar("dest_addr");
msg->par("dest_addr") = 168;
...
long dest_addr = msg->par("dest_addr");
6.2.1 Introduction
In practice, you’ll need to add various fields to cMessage to make it useful. For example,
if you’re modelling packets in communication networks, you need to have a way to store
protocol header fields in message objects. Since the simulation library is written in C++,
the natural way of extending cMessage is via subclassing it. However, because for each
field you need to write at least three things (a private data member, a getter and a setter
method), and the resulting class has to integrate with the simulation framework, writing the
necessary C++ code can be a tedious and time-consuming task.
OMNeT++ offers a more convenient way called message definitions. Message definitions pro-
vide a very compact syntax to describe message contents. C++ code is automatically gener-
ated from message definitions, saving you a lot of typing.
A common source of complaint about code generators in general is lost flexibility: if you have
a different idea how the generated code should look like, there’s little you can do about it. In
OMNeT++, however, there’s nothing to worry about: you can customize the generated class
to any extent you like. Even if you decide to heavily customize the generated class, message
definitions still save you a great deal of manual work.
The message subclassing feature in OMNeT++ is still somewhat experimental, meaning that:
• The message description syntax and features may slightly change in the future, based
on feedback from the community;
• The compiler that translates message descriptions into C++ is a perl script opp_msgc.
This is a temporary solution until the C++-based nedtool is finished.
The subclassing approach for adding message parameters was originally suggested by Nim-
rod Mesika.
Let us begin with a simple example. Suppose that you need message objects to carry source
and destination addresses as well as a hop count. You could write a mypacket.msg file with
the following contents:
102
OMNeT++ Manual – Messages
message MyPacket
{
fields:
int srcAddress;
int destAddress;
int hops = 32;
};
The task of the message subclassing compiler is to generate C++ classes you can use from
your models as well as “reflection” classes that allow Tkenv to inspect these data stuctures.
If you process mypacket.msg with the message subclassing compiler, it will create the fol-
lowing files for you: mypacket_m.h and mypacket_m.cc. mypacket_m.h contains the dec-
laration of the MyPacket C++ class, and it should be included into your C++ sources where
you need to handle MyPacket objects.
The generated mypacket_m.h will contain the following class declaration:
So in your C++ file, you could use the MyPacket class like this:
#include "mypacket_m.h"
...
MyPacket *pkt = new MyPacket("pkt");
pkt->setSrcAddress( localAddr );
...
The mypacket_m.cc file contains implementation of the generated MyPacket class, as well
as “reflection” code that allows you to inspect these data stuctures in the Tkenv GUI. The
mypacket_m.cc file should be compiled and linked into your simulation. (If you use the
opp_makemake tool to generate your makefiles, the latter will be automatically taken care
of.)
There’s might be some misunderstanding around the purpose and concept of message defini-
tions, so it seems to be a good idea to deal with them right here.
It is not:
• ... an attempt to reproduce the functionality of C++ with another syntax. Do not look for
complex C++ types, templates, conditional compilation, etc. Also, it defines data only
(or rather: an interface to access data) – not any kind of active behaviour.
• ... a generic class generator. This is meant for defining message contents, and data
structure you put in messages. Defining methods is not supported on purpose. Also,
while you can probably (ab)use the syntax to generate classes and structs used inter-
nally in simple modules, this is probably not a good idea.
103
OMNeT++ Manual – Messages
The goal is to define the interface (getter/setter methods) of messages rather than their im-
plementations in C++. A simple and straightforward implementation of fields is provided –
if you’d like a different internal representation for some field, you can have it by customizing
the class.
There are questions you might ask:
• Why doesn’t it support std::vector and other STL classes? Well, it does... Message
definitions focus on the interface (getter/setter methods) of the classes, optionally leav-
ing the implementation to you – so you can implement fields (dynamic array fields)
using std::vector. (This aligns with the idea behind STL – it was designed to be
nuts and bolts for C++ programs).
• Why does it support C++ data types and not octets, bytes, bits, etc..? That would restrict
the scope of message definitions to networking, and OMNeT++ wants to support other
application areas as well. Furthermore, the set of necessary concepts to be supported
is probably not bounded, there would always be new data types to be adopted.
• Why no embedded classes? Good question. As it does not conflict with the above princi-
ples, it might be added someday.
The following sections describe the message syntax and features in detail.
enum ProtocolTypes
{
IP = 1;
TCP = 2;
};
message FooPacket
{
fields:
int sourceAddress;
int destAddress;
bool hasPayload;
};
Processing this description with the message compiler will produce a C++ header file with a
generated class, FooPacket. FooPacket will be a subclass of cMessage.
104
OMNeT++ Manual – Messages
For each field in the above description, the generated class will have a protected data mem-
ber, a getter and a setter method. The names of the methods will begin with get and set,
followed by the field name with its first letter converted to uppercase. Thus, FooPacket will
contain the following methods:
Note that the methods are all declared virtual to give you the possibility of overriding them
in subclasses.
Two constructors will be generated: one that optionally accepts object name and (for cMes-
sage subclasses) message kind, and a copy constructor:
Appropriate assignment operator (operator=()) and dup() methods will also be generated.
Data types for fields are not limited to int and bool. You can use the following primitive
types (i.e. primitive types as defined in the C++ language):
• bool
• char, unsigned char
• short, unsigned short
• int, unsigned int
• long, unsigned long
• double
Initial values
message FooPacket
{
fields:
int sourceAddress = 0;
int destAddress = 0;
bool hasPayload = false;
};
105
OMNeT++ Manual – Messages
Enum declarations
You can declare that an int (or other integral type) field takes values from an enum. The
message compiler can than generate code that allows Tkenv display the symbolic value of
the field.
Example:
message FooPacket
{
fields:
int payloadType enum(PayloadTypes);
};
Fixed-size arrays
message FooPacket
{
fields:
long route[4];
};
The generated getter and setter methods will have an extra k argument, the array index:
If you call the methods with an index that is out of bounds, an exception will be thrown.
Dynamic arrays
If the array size is not known in advance, you can declare the field to be a dynamic array:
message FooPacket
{
fields:
long route[];
};
In this case, the generated class will have two extra methods in addition to the getter and
setter methods: one for setting the array size, and another one for returning the current
array size.
106
OMNeT++ Manual – Messages
The set...ArraySize() method internally allocates a new array. Existing values in the
array will be preserved (copied over to the new array.)
The default array size is zero. This means that you need to call the set...ArraySize()
before you can start filling array elements.
String members
message FooPacket
{
fields:
string hostName;
};
The generated getter and setter methods will return and accept const char* pointers:
The generated object will have its own copy of the string.
NOTE: a string member is different from a character array, which is treated as an array of
any other type. For example,
message FooPacket
{
fields:
char chars[10];
};
• set up a hierarchy of message (packet) classes, that is, not only subclass from cMessage
but also from your own message classes;
• use not only primitive types as fields, but also structs, classes or typedefs. Sometimes
you’ll want to use a C++ type present in an already existing header file, another time
you’ll want a struct or class to be generated by the message compiler so that you can
benefit from Tkenv inspectors.
107
OMNeT++ Manual – Messages
By default, messages are subclassed from cMessage. However, you can explicitly specify the
base class using the extends keyword:
For the above example, the generated C++ code will look like:
Inheritance also works for structs and classes (see next sections for details).
Defining classes
Until now we have used the message keyword to define classes, which implies that the base
class is cMessage, either directly or indirectly.
But as part of complex messages, you’ll need structs and other classes (rooted or not rooted in
cObject) as building blocks. Classes can be created with the class class keyword; structs
we’ll cover in the next section.
The syntax for defining classes is almost the same as defining messages, only the class
keyword is used instead of message.
Slightly different code is generated for classes that are rooted in cObject than for those
which are not. If there is no extends, the generated class will not be derived from cObject,
thus it will not have name(), className(), etc. methods. To create a class with those
methods, you have to explicitly write extends cObject.
You can define C-style structs to be used as fields in message classes, “C-style” meaning
“containing only data and no methods”. (Actually, in the C++ language a struct can have
methods, and in general it can do anything a class can.)
The syntax is similar to that of defining messages:
struct MyStruct
{
fields:
char array[10];
short version;
};
108
OMNeT++ Manual – Messages
However, the generated code is different. The generated struct has no getter or setter meth-
ods, instead the fields are represented by public data members. For the above definition, the
following code is generated:
// generated C++
struct MyStruct
{
char array[10];
short version;
};
A struct can have primitive types or other structs are fields. It cannot have string or class as
field.
Inheritance is supported for structs:
struct Base
{
...
};
In addition to primitive types, you can also use other structs or objects as a field. For example,
if you have a struct named IPAddress, you can write the following:
message FooPacket
{
fields:
IPAddress src;
};
The IPAddress structure must be known in advance to the message compiler; that is, it
must either be a struct or class defined earlier in the message description file, or it must be a
C++ type with its header file included via cplusplus {{...}} and its type announced (see
Announcing C++ types).
The generated class will contain an IPAddress data member (that is, not a pointer to an
IPAddress). The following getter and setter methods will be generated:
109
OMNeT++ Manual – Messages
Pointers
If you want to use one of your own types (a class, struct or typedef, declared in a C++ header)
in a message definition, you have to announce those types to the message compiler. You also
have to make sure that your header file gets included into the generated _m.h file so that
the C++ compiler can compile it.
Suppose you have an IPAddress structure, defined in an ipaddress.h file:
// ipaddress.h
struct IPAddress {
int byte0, byte1, byte2, byte3;
};
To be able to use IPAddress in a message definition, the message file (say foopacket.msg)
should contain the following lines:
cplusplus {{
#include "ipaddress.h"
}};
struct IPAddress;
The effect of the first three lines is simply that the #include statement will be copied into
the generated foopacket_m.h file to let the C++ compiler know about the IPAddress class.
The message compiler itself will not try to make sense of the text in the body of the cplus-
plus {{ ... }} directive.
The next line, struct IPAddress, tells the message compiler that IPAddress is a C++
struct. This information will (among others) affect the generated code.
Classes can be announced using the class keyword:
class cSubQueue;
The above syntax assumes that the class is derived from cObject either directly or indi-
rectly. If it is not, the noncobject keyword should be used:
The distinction between classes derived and not derived from cObject is important because
the generated code differs at places. The generated code is set up so that if you incidentally
forget the noncobject keyword (and so you mislead the message compiler into thinking
that your class is rooted in cObject when in fact it is not), you’ll get a C++ compiler error in
the generated header file.
110
OMNeT++ Manual – Messages
Sometimes you need the generated code to something more or do something differently than
the version generated by the message compiler. For example, when setting a integer field
named payloadLength, you might also need to adjust the packet length. That is, the fol-
lowing default (generated) version of the setPayloadLength() method is not suitable:
According to common belief, the largest drawback of generated code is that it is difficult or
impossible to fulfill such wishes. Hand-editing of the generated files is worthless, because
they will be overwritten and changes will be lost in the code generation cycle.
However, object oriented programming offers a solution. A generated class can simply be
customized by subclassing from it and redefining whichever methods need to be different
from their generated versions. This practice is known as the Generation Gap design pattern.
It is enabled with the following syntax:
message FooPacket
{
properties:
customize = true;
fields:
int payloadLength;
};
The properties section within the message declaration contains meta-info that affects how
generated code will look like. The customize property enables the use of the Generation Gap
pattern.
If you process the above code with the message compiler, the generated code will contain a
FooPacket_Base class instead of FooPacket. The idea is that you have to subclass from
FooPacket_Base to produce FooPacket, while doing your customizations by redefining the
necessary methods.
111
OMNeT++ Manual – Messages
There is a minimum amount of code you have to write for FooPacket, because not everything
can be pre-generated as part of FooPacket_Base, e.g. constructors cannot be inherited. This
minimum code is the following (you’ll find it the generated C++ header too, as a comment):
Register_Class(FooPacket);
So, returning to our original example about payload length affecting packet length, the code
you’d write is the following:
Abstract fields
The purpose of abstract fields is to let you to override the way the value is stored inside the
class, and still benefit from inspectability in Tkenv.
For example, this is the situation when you want to store a bitfield in a single int or short,
and still you want to present bits as individual packet fields. It is also useful for implement-
ing computed fields.
You can declare any field to be abstract with the following syntax:
112
OMNeT++ Manual – Messages
message FooPacket
{
properties:
customize = true;
fields:
abstract bool urgentBit;
};
For an abstract field, the message compiler generates no data member, and generated
getter/setter methods will be pure virtual:
Usually you’ll want to use abstract fields together with the Generation Gap pattern, so that
you can immediately redefine the abstract (pure virtual) methods and supply your imple-
mentation.
6.2.7 Summary
This section attempts to summarize the possibilities.
You can generate:
• primitive types: bool, char, short, int, long, unsigned short, unsigned int,
unsigned long, double
• string, a dynamically allocated string, presented as const char *
• fixed-size arrays of the above types
• structs, classes (both rooted and not rooted in cObject), declared with the message
syntax or externally in C++ code
• variable-sized arrays of the above types (stored as a dynamically allocated array plus
an integer for the array size)
Further features:
113
OMNeT++ Manual – Messages
Generated code (all generated methods are virtual, although this is not written out in the
following table):
string type
string field; const char *getField();
void setField(const char *);
fixed-size arrays
double field[4]; double getField(unsigned k);
void setField(unsigned k, double d);
unsigned getFieldArraySize();
dynamic arrays
double field[]; void setFieldArraySize(unsigned n);
unsigned getFieldArraySize();
double getField(unsigned k);
void setField(unsigned k, double d);
customized class
class Foo { class Foo_Base { ... };
properties:
customize=true; and you have to write:
class Foo : public Foo_Base {
...
};
abstract fields
abstract double field;
double getField() = 0;
void setField(double d) = 0;
Example simulations
Several of the example simulations (Token Ring, Dyna2, Hypercube) use message definitions.
For example, in Dyna2 you’ll find this:
114
OMNeT++ Manual – Messages
• other model files (client.cc, server.cc, ...) use the generated message classes
You’d notice one drawback of this solution when you try to use Tkenv for debugging. While
cPar-based message parameters can be viewed in message inspector windows, fields added
via subclassing do not appear there. The reason is that Tkenv, being just another C++ li-
brary in your simulation program, doesn’t know about your C++ instance variables. The
problem cannot be solved entirely within Tkenv, because the C++ language does not support
“reflection” (extracting class information at runtime) like for example Java does.
There is a solution however: one can supply Tkenv with missing “reflection” information
about the new class. Reflection info might take the form of a separate C++ class whose
methods return information about the RadioMsg fields. This descriptor class might look like
this:
115
OMNeT++ Manual – Messages
Then you have to inform Tkenv that a RadioMsgDescriptor exists and that it should be
used whenever Tkenv finds messages of type RadioMsg (as it is currently implemented,
whenever the object’s className() method returns "RadioMsg"). So when you inspect a
RadioMsg in your simulation, Tkenv can use RadioMsgDescriptor to extract and display
the values of the freq and power variables.
The actual implementation is somewhat more complicated than this, but not much.
116
OMNeT++ Manual – The Simulation Library
Chapter 7
OMNeT++ has an extensive C++ class library which you can use when implementing simple
modules. Some areas of class librar have already been covered in the previous chapters:
• events, messages, network packets: the cMessage and cPacket classes (chapter 6)
• sending and receiving messages, scheduling and cancelling events, terminating the
module or the simulation (section 5.6)
• access to module gates and parameters via cModule member functions (sections 5.7
and 5.8)
• making variables inspectable in the graphical user interface (Tkenv): the WATCH()
macro (cWatch class)
• sending debug output to and prompting for user input in the graphical user interface
(Tkenv): the ev object (cEnvir class)
117
OMNeT++ Manual – The Simulation Library
Classes in the OMNeT++ simulation library are derived from cObject. Functionality and
conventions that come from cObject:
• name attribute
• className() member and other member functions giving textual information about
the object
• support for automatic cleanup (garbage collection) at the end of the simulation
Classes inherit and redefine several cObject member functions; in the following we’ll dis-
cuss some of the practically important ones.
Member functions that set and query object attributes follow consistent naming. The setter
member function has the form setSomething(...) and its getter counterpart is named some-
thing(), i.e. the “get” verb found in Java and some other libraries is omitted for brevity. For
example, the length attribute of the cMessage class can be set and read like this:
msg->setLength( 1024 );
length = msg->length();
className()
For each class, the className() member function returns the class name as a string:
Name attribute
An object can be assigned a name (a character string). The name string is the first argument
to the constructor of every class, and it defaults to NULL (no name string). If you supply a
name string, the object will make its own copy (strdup()). As an example, you can create a
message object like this:
You can also set the name after the object has been created:
mymsg->setName("mymsg");
118
OMNeT++ Manual – The Simulation Library
You can get a pointer to the internally stored copy of the name string like this:
For convenience and efficiency reasons, the empty string “” and NULL are treated as equiv-
alent by library objects: “” is stored as NULL (so that it does not consume heap), but it is
returned as “” (so that it is easier to print out etc). Thus, if you create a message object with
either NULL or “” as name, it will be stored as NULL and name() will return a pointer to “”,
a static string:
Objects have two more member functions which return other sort of names based on the
name attribute: fullName() and fullPath().
Suppose we have a module in the network university_lan, compound module fddi_ring, sim-
ple module station[10]. If you call the functions on the simple module object (cSimpleMod-
ule inherits from cObject, too), the functions will return these values:
These functions work for any object. For example, a local object inside the module would
produce results like this:
void FDDIStation::activity()
{
cQueue buffer("buffer");
ev << buffer->fullPath(); // --> "university_lan.fddi_ring.
// station[10].buffer"
}
fullName() and fullPath(), together with className() can be used for example to gen-
erate informative error messages.
Be aware that fullName() and fullPath() return pointers to static buffers. Each call will
overwrite the previous content of the buffer, so for example you shouldn’t put two calls in a
single printf() statement:
The dup() member function creates an exact copy of the object, duplicating contained objects
also if necessary. This is especially useful in the case of message objects. dup() returns a
pointer of type cObject*, so it needs to be cast to the proper type:
119
OMNeT++ Manual – The Simulation Library
dup() works through calling the copy constructor, which in turn relies on the assignment
operator between objects. operator=() can be used to copy contents of an object into an-
other object of the same type. The copying is done properly; object contained in the object will
also be duplicated if necessary. For various reasons, operator=() does not copy the name
string; the copy constructor does it.
Iterators
There are several container classes in the library (cQueue, cArray etc.) For many of them,
there is a corresponding iterator class that you can use to loop through the objects stored in
the container.
For example:
cQueue queue;
//..
for (cQueue::Iterator queueIter(queue); !queueIter.end(); queueIter++)
{
cObject *containedObject = queueIter();
}
Ownership control
By default, if a container object is destroyed, it destroys the contained objects too. If you call
dup(), the contained objects are duplicated too for the new container. This is done so be-
cause contained objects are owned by the container; ownership is defined as the right/duty of
deallocation. However, there is a fine-grain ownership control mechanism built in which al-
lows you to specify on per-object basis whether you want objects to be owned by the container
or not; by calling the takeOwnership() member function with false, you tell the container
that you don’t want it to become the owner of objects that will be inserted in the future.
The ownership mechanism is discussed in detail in section 7.13
7.2 Utilities
Tracing
The tracing feature will be used extensively in the code examples, so it is shortly introduced
here. It will be covered in detail in a later section.
The ev object represents the user interface of the simulation. You can send debugging output
to ev with the C++-style output operators:
120
OMNeT++ Manual – The Simulation Library
There are utility functions which convert simulation time (simtime_t) to a printable string
(like "3s 130ms 230us") and vica versa.
The simtimeToStr() function converts a simtime_t (passed in the first arg) to textual
form. The result is placed into the buffer pointed to by the second arg. If the second arg is
omitted or it is NULL, simtimeToStr() will place the result into a static buffer which is
overwritten with each call:
char buf[32];
ev.printf("t1=%s, t2=%s\n", simtimeToStr(t1), simTimeToStr(t2,buf));
The strToSimtime() function parses a time specification passed in a string, and returns a
simtime_t. If the string cannot be entirely interpreted, -1 is returned.
Another variant, strToSimtime0() can be used if the time string is a substring in a larger
string. Instead of taking a char*, it takes a reference to char* (char*&) as the first argument.
The function sets the pointer to the first character that could not be interpreted as part of
the time string, and returns the value. It never returns -1; if nothing at the beginning of the
string looked like simulation time, it returns 0.
/dev/random device.
121
OMNeT++ Manual – The Simulation Library
The currently used random number generator in OMNeT++ is a linear congruential gener-
ator (LCG) with a cycle length of 231 − 2. The startup code of OMNeT++ contains code that
checks if the random number generator works OK, so you do not have to worry about this if
you port the simulator to a new architecture or use a different compiler.
The random number generator was taken from [Jai91], pp. 441-444,455. It has the following
properties:
• Range: 1...231 − 2
• Method: x := (x ∗ 75 )mod(231 − 1)
long intrand()
{
const long int a=16807, q=127773, r=2836;
seed=a*(seed%q) - r*(seed/q);
if (seed<=0) seed+=INTRAND_MAX;
return seed;
}
Caution!
The above “minimal standard” RNG is only suitable for small-scale simulation studies. As
shown by Karl Entacher et al. in [EHW02], the cycle length of about 231 is too small (on
todays fast computers it is easy to exhaust all random numbers), and the structure of the
generated “random” points is too regular. The [Hel98] paper gives you a broader overview of
issues associated with RNGs used for simulation, and it’s well worth reading. It also gives
you useful links and references to further reading on the topic.
Work is underway to create a flexible and extensible random number architecture in future
versions of OMNeT++, and to integrate modern RNGs such as L’Ecuyer’s CMRG [LSCK02]
with a period of about 2191 and/or Mersenne Twister [MN98].
Multiple RNGs
If a simulation program uses random numbers for more than one purpose, the numbers
should come from different random number generators. OMNeT++ provides several indepen-
dent random number generators (by default 32; this number is #defined as NUM_RANDOM_GENE-
RATORS in utils.h).
To avoid unwanted correlation, it is also important that different simulation runs and differ-
ent random number sources within one simulation run use non-overlapping series of random
numbers, so the generators should be started with seeds well apart. For selecting good seeds,
the seedtool program can be used (it is documented later).
122
OMNeT++ Manual – The Simulation Library
Accessing RNGs
The random number seed can be specified in the ini file (random-seed=) or set directly from
within simple modules with the randseed() function:
Function Description
Continuous distributions
uniform(a, b, rng=0) uniform distribution in the range [a,b)
exponential(mean, rng=0) exponential distribution with the given mean
normal(mean, stddev, rng=0) normal distribution with the given mean and
standard deviation
truncnormal(mean, stddev, normal distribution truncated to nonnegative
rng=0) values
gamma_d(alpha, beta, rng=0) gamma distribution with parameters alpha>0,
beta>0
123
OMNeT++ Manual – The Simulation Library
They are the same functions that can be used in NED files. intuniform() generates inte-
gers including both the lower and upper limit, so for example the outcome of tossing a coin
could be written as intuniform(1,2). truncnormal() is the normal distribution truncated to
nonnegative values; its implementation generates a number with normal distribution and if
the result is negative, it keeps generating other numbers until the outcome is nonnegative.
If the above distributions do not suffice, you can write your own functions. If you register
your functions with the Register_Function() macro, you can use them in NED files and
ini files too.
You can also specify your distribution as a histogram. The cLongHistogram, cDouble-
Histogram, cVarHistogram, cKSplit or cPSquare classes are there to generate random
numbers from equidistant-cell or equiprobable-cell histograms. This feature is documented
later, with the statistical classes.
124
OMNeT++ Manual – The Simulation Library
cQueue is a container class that acts as a queue. cQueue can hold objects of type derived
from cObject (almost all classes from the OMNeT++ library), such as cMessage, cPar, etc.
Internally, cQueue uses a double-linked list to store the elements.
A queue object has a head and a tail. Normally, new elements are inserted at its head and
elements are removed at its tail.
The basic cQueue member functions dealing with insertion and removal are insert() and
pop(). They are used like this:
cQueue queue("my-queue");
cMessage *msg;
// insert messages
for (int i=0; i<10; i++)
{
msg = new cMessage;
queue.insert( msg );
}
// remove messages
while( ! queue.empty() )
{
msg = (cMessage *)queue.pop();
delete msg;
}
The length() member function returns the number of items in the queue, and empty()
tells whether there’s anything in the queue.
There are other functions dealing with insertion and removal. The insertBefore() and
insertAfter() functions insert a new item exactly before and after a specified one, regard-
less of the ordering function.
The tail() and head() functions return pointers to the objects at the tail and head of the
queue, without affecting queue contents.
The pop() function can be used to remove items from the tail of the queue, and the re-
move() function can be used to remove any item known by its pointer from the queue:
125
OMNeT++ Manual – The Simulation Library
queue.remove( msg );
Priority queue
By default, cQueue implements a FIFO, but it can also act as a priority queue, that is, it
can keep the inserted objects ordered. If you want to use this feature, you have to provide a
function that takes two cObject pointers, compares the two objects and returns -1, 0 or 1 as
the result (see the reference for details). An example of setting up an ordered cQueue:
If the queue object is set up as an ordered queue, the insert() function uses the ordering
function: it searches the queue contents from the head until it reaches the position where
the new item needs to be inserted, and inserts it there.
Iterators
Normally, you can only access the objects at the head or tail of the queue. However, if you
use an iterator class, cQueue::Iterator, you can examine each object in the queue.
The cQueue::Iterator constructor takes two arguments, the first is the queue object and
the second one specifies the initial position of the iterator: 0=tail, 1=head. Otherwise it acts
as any other OMNeT++ iterator class: you can use the ++ and – operators to advance it, the
() operator to get a pointer to the current item, and the end() member function to examine
if you’re at the end (or the beginning) of the queue.
An example:
cArray is a container class that holds objects derived from cObject. cArray stores the
pointers of the objects inserted instead of making copies. cArray works as an array, but
if it gets full, it grows automatically. Internally, cArray is implemented with an array of
pointers; if the array gets full, it is reallocated.
cArray objects are used in OMNeT++ to store parameters attached to messages, and inter-
nally, for storing module parameters and gates.
Creating an array:
cArray array("array");
126
OMNeT++ Manual – The Simulation Library
Adding an object at a given index (if the index is occupied, you’ll get an error message):
You can also search the array or get a pointer to an object by the object’s name:
You can remove an object from the array by calling remove() with the object name, the
index position or the object pointer:
array.remove("par");
array.remove(index);
array.remove( p );
The remove() function doesn’t deallocate the object, but it returns the object pointer. If you
also want to deallocate it, you can write:
Iteration
cArray has no iterator, but it’s easy to loop through all the indices with an integer variable.
The items() member function returns the largest index plus one.
127
OMNeT++ Manual – The Simulation Library
• as module parameters
• as message parameters
There are many ways to set a cPar’s value. One is the set...Value() member functions:
cPar pp("pp");
pp.setDoubleValue(1.0);
cPar pp("pp");
pp = 1.0;
For reading its value, it is best to use overloaded type cast operators:
double d1 = (double)pp;
// or simply:
double d2 = pp;
Long integers:
pp = 89363L; // or:
pp.setLongValue( 89363L );
Character string:
The cPar object makes its own copy of the string, so the original one does not need to be
preserved. Short strings (less than ∼20 chars) are handled more efficiently because they are
stored in the object’s memory space (and are not dynamically allocated).
There are several other types cPar can store: such as boolean, void* pointer; cObject*
pointer, function with constant args; they will be mentioned in the next section.
For numeric and string types, an input flag can be set. In this case, when the object’s value
is first used, the parameter value will be searched for in the configuration (ini) file; if it is not
found there, the user will be given a chance to enter the value interactively.
Examples:
cPar inp("inp");
inp.setPrompt("Enter my value:");
inp.setInput( true ); // make it an input parameter
double a = (double)inp; // the user will be prompted HERE
128
OMNeT++ Manual – The Simulation Library
cPar rnd("rnd");
rnd.setDoubleValue(intuniform, -10.0, 10.0);// uniform distr.
rnd.setDoubleValue(normal, 100.0, 5.0); // normal distr. (mean,dev)
rnd.setDoubleValue(exponential, 10.0); // exponential distr. (mean)
intuniform(), normal() etc. are ordinary C functions taking double arguments and re-
turning double. Each time you read the value of a cPar containing a function like above,
the function will be called with the given constant arguments (e.g. normal(100.0,5.0)) and its
return value used.
The above functions use number 0 from the several random number generators. To use
another generator, use the genk_xxx versions of the random functions:
A cPar object can also be set to return a random variable from a distribution collected by a
statistical data collection object:
To get the store pointer back, you can use typecast or the objectValue() member function:
Whether the cPar object will own the other object or not is controlled by the takeOwner-
ship() member function, just as with container classes. This is documented in detail in the
class library reference. By default, cPar will own the object.
cPar can be used to store non-object pointers (for example C structs) or non-OMNeT++ object
types in the parameter object. It works very similarly to the above mechanism. An example:
129
OMNeT++ Manual – The Simulation Library
The configPointer() setting only affects what happens when the cPar is deleted, dupli-
cated or copied, but does not apply to assigning new pointers. That is, if you assign a new
void* to the cPar, you simply overwrite the pointer – the block denoted by the old pointer
is not deleted. This fact can be used to extract some dynamically allocated block out of the
cPar: carrying on the previous example, you would extract the array of 15 doubles from the
cPar like this:
However, if you assign some non-pointer value to the cPar, beware: this will activate the
memory management for the block. If you temporarily use the same cPar object to store
other than void* (’P’) values, the configPointer() setting is lost.
cPar expr("expr");
expr.setDoubleValue(expression,5);
The cPar object created above contains the (count + 1)/2 expression where count is a module
parameter. Each time the cPar is evaluated, it recalculates the expression, using the current
value of count. Note the & sign in front of par(”count”) expression: if it was not there, the
130
OMNeT++ Manual – The Simulation Library
parameter would be taken by value, evaluated once and then the resulting constant would
be used.
Another example is a distribution with mean and standard deviation given by module pa-
rameters:
cPar expr("expr");
expr.setDoubleValue(expression,3);
For more information, see the reference and the code NEDC generates for parameter expres-
sions.
Redirection is how module parameters taken by reference are implemented. The redirection
does not include name strings. That is, if you say A->setName(”newname”) in the above
example, A’s name will be changed as the name member is not redirected. (This is natural
if you consider parameters taken by reference: a parameter should/can have different name
than the value it refers to.)
You create a redirection with the setRedirection() function:
131
OMNeT++ Manual – The Simulation Library
The only way to determine whether a is really holding the value or it is redirected to another
cPar is to use the isRedirected() member function which returns a bool, or redirec-
tion() which returns the pointer to the background object, or NULL if there’s no redirec-
tion:
To break the link between the two objects, use the cancelRedirection() member function.
(No other method will work, including assigning a the value of another cPar object.) The
cancelRedirection() function gives the (long)0 value to the redirected object (the other
will be unaffected). If you want to cancel the indirection but keep the old value, you can do
something like this:
The full table of type characters is presented in the Summary section below.
The isNumeric() function tells whether the object stores one of the numeric types, so that
e.g. asDoubleValue() can be invoked on it.
7.6.7 Summary
The various cPar types and the member functions used to manipulate them are summarized
in the following table:
132
OMNeT++ Manual – The Simulation Library
133
OMNeT++ Manual – The Simulation Library
7.7.1 Overview
The cTopology class was designed primarily to support routing in telecommunication or
multiprocessor networks.
A cTopology object stores an abstract representation of the network in graph form:
You can specify which modules (either simple or compound) you want to include in the graph.
The graph will include all connections among the selected modules. In the graph, all nodes
are at the same level, there’s no submodule nesting. Connections which span across com-
pound module boundaries are also represented as one graph edge. Graph edges are directed,
just as module gates are.
If you’re writing a router or switch model, the cTopology graph can help you determine
what nodes are available through which gate and also to find optimal routes. The cTopology
object can calculate shortest paths between nodes for you.
The mapping between the graph (nodes, edges) and network model (modules, gates, connec-
tions) is preserved: you can easily find the corresponding module for a cTopology node and
vica versa.
• by module type
• by a parameter’s presence and its value
• with a user-supplied boolean function
First, you can specify which node types you want to include. The following code extracts all
modules of type Router or User. (Router and User can be both simple and compound module
types.)
cTopology topo;
topo.extractByModuleType( "Router", "User", NULL );
Any number of module types (up to 32) can be supplied; the list must be terminated by
NULL.
Second, you can extract all modules which have a certain parameter:
topo.extractByParameter( "ip_address" );
You can also specify that the parameter must have a certain value for the module to be
included in the graph:
134
OMNeT++ Manual – The Simulation Library
The third form allows you to pass a function which can determine for each module whether
it should or should not be included. You can have cTopology pass supplemental data to the
function through a void* pointer. An example which selects all top-level modules (and does
not use the void* pointer):
A cTopology object uses two types: sTopoNode for nodes and sTopoLink for edges. (sTopo-
LinkIn and sTopoLinkOut are ‘aliases’ for sTopoLink; we’ll talk about them later.)
Once you have the topology extracted, you can start exploring it. Consider the following code
(we’ll explain it shortly):
The nodes() member function (1st line) returns the number of nodes in the graph, and
node(i) returns a pointer to the ith node, an sTopoNode structure.
The correspondence between a graph node and a module can be obtained by:
The nodeFor() member function returns a pointer to the graph node for a given module. (If
the module is not in the graph, it returns NULL). nodeFor() uses binary search within the
cTopology object so it is fast enough.
sTopoNode’s other member functions let you determine the connections of this node: in-
Links(), outLinks() return the number of connections, in(i) and out(i) return point-
ers to graph edge objects.
By calling member functions of the graph edge object, you can determine the modules and
gates involved. The remoteNode() function returns the other end of the connection, and lo-
calGate(), remoteGate(), localGateId() and remoteGateId() return the gate point-
ers and ids of the gates involved. (Actually, the implementation is a bit tricky here: the same
graph edge object sTopoLink is returned either as sTopoLinkIn or as sTopoLinkOut so
that “remote” and “local” can be correctly interpreted for edges of both directions.)
135
OMNeT++ Manual – The Simulation Library
This performs the Dijkstra algorithm and stores the result in the cTopology object. The
result can then be extracted using cTopology and sTopoNode methods. Naturally, each
call to unweightedSingleShortestPathsTo() overwrites the results of the previous call.
Walking along the path from our module to the target node:
if (node == NULL)
{
ev < "We (" << fullPath() << ") are not included in the topology.\n";
}
else if (node->paths()==0)
{
ev << "No path to destination.\n";
}
else
{
while (node != topo.targetNode())
{
ev << "We are in " << node->module()->fullPath() << endl;
ev << node->distanceToTarget() << " hops to go\n";
ev << "There are " << node->paths()
<< " equally good directions, taking the first one\n";
sTopoLinkOut *path = node->path(0);
ev << "Taking gate " << path->localGate()->fullName()
<< " we arrive in " << path->remoteNode()->module()->fullPath()
<< " on its gate " << path->remoteGate()->fullName() << endl;
node = path->remoteNode();
}
}
136
OMNeT++ Manual – The Simulation Library
You can enable/disable nodes or edges in the graph. This is done by calling their enable()
or disable() member functions. Disabled nodes or edges are ignored by the shortest paths
calculation algorithm. The enabled() member function returns the state of a node or edge
in the topology graph.
One usage of disable() is when you want to determine in how many hops the target node
can be reached from our node through a particular output gate. To calculate this, you calcu-
late the shortest paths to the target from the neighbor node, but you must disable the current
node to prevent the shortest paths from going through it:
unweightedMultiShortestPathsTo(sTopoNode *target);
weightedSingleShortestPathsTo(sTopoNode *target);
weightedMultiShortestPathsTo(sTopoNode *target);
• cStdDev keeps number of samples, mean, standard deviation, minimum and maxi-
mum value etc.
• cWeightedStdDev is similar to cStdDev, but accepts weighted observations. cWeight-
edStdDev can be used for example to calculate time average. It is the only weighted
statistics class.
• cLongHistogram and cDoubleHistogram are descendants of cStdDev and also keep
an approximation of the distribution of the observations using equidistant (equal-sized)
cell histograms.
• cVarHistogram implements a histogram where cells do not need to be the same size.
You can manually add the cell (bin) boundaries, or alternatively, automatically have a
partitioning created where each bin has the same number of observations (or as close
to that as possible).
• cPSquare is a class that uses the P 2 algorithm described in [JC85]. The algorithm
calculates quantiles without storing the observations; one can also think of it as a his-
togram with equiprobable cells.
137
OMNeT++ Manual – The Simulation Library
Basic usage
One can insert an observation into a statistic object with the collect() function or the
+= operator (they are equivalent). cStdDev has the following methods for getting statistics
out of the object: samples(), min(), max(), mean(), stddev(), variance(), sum(),
sqrSum() with the obvious meanings. An example usage for cStdDev:
cStdDev stat("stat");
The distribution estimation classes (the histogram classes, cPSquare and cKSplit) are de-
rived from cDensityEstBase. Distribution estimation classes (except for cPSquare) as-
sume that the observations are within a range. You may specify the range explicitly (based
on some a-priori info about the distribution) or you may let the object collect the first few ob-
servations and determine the range from them. Methods which let you specify range settings
are part of cDensityEstBase.
The following member functions exist for setting up the range and to specify how many
observations should be used for automatically determining the range.
setRange(lower,upper);
setRangeAuto(num_firstvals, range_ext_factor);
setRangeAutoLower(upper, num_firstvals, range_ext_factor);
setRangeAutoUpper(lower, num, range_ext_factor);
setNumFirstVals(num_firstvals);
The following example creates a histogram with 20 cells and automatic range estimation:
Here, 20 is the number of cells (not including the underflow/overflow cells, see later), and
100 is the number of observations to be collected before setting up the cells. 1.5 is the range
extension factor. It means that the actual range of the initial observations will be expanded
1.5 times and this expanded range will be used to lay out the cells. This method increases
138
OMNeT++ Manual – The Simulation Library
the chance that further observations fall in one of the cells and not outside the histogram
range.
After the cells have been set up, collecting can go on.
The transformed() function returns true when the cells have already been set up. You can
force range estimation and setting up the cells by calling the transform() function.
The observations that fall outside the histogram range will be counted as underflows and
overflows. The number of underflows and overflows are returned by the underflowCell()
and overflowCell() member functions.
Afterwards, a cPSquare can be used with the same member functions as a histogram.
There are three member functions to explicitly return cell boundaries and the number of
observations is each cell. cells() returns the number of cells, basepoint(int k) re-
turns the kth base point, cell(int k) returns the number of observations in cell k, and
cellPDF(int k) returns the PDF value in the cell (i.e. between basepoint(k) and base-
point(k+1)). These functions work for all histogram types, plus cPSquare and cKSplit.
An example:
long n = histogram.samples();
for (int i=0; i<histogram.cells(); i++)
{
double cellWidth = histogram.basepoint(i+1)-histogram.basepoint(i);
int count = histogram.cell(i);
double pdf = histogram.cellPDF(i);
//...
}
The pdf(x) and cdf(x) member functions return the value of the probability density func-
tion and the cumulated density function at a given x, respectively.
139
OMNeT++ Manual – The Simulation Library
The random() member function generates random numbers from the distribution stored by
the object:
cPar rnd_par("rnd_par");
rnd_par.setDoubleValue(&histogram);
The cPar object stores the pointer to the histogram (or P 2 object), and whenever it is asked
for the value, calls the histogram object’s random() function:
Storing/loading distributions
The statistic classes have loadFromFile() member functions that read the histogram data
from a text file. If you need a custom distribution that cannot be written (or it is inefficient) as
a C function, you can describe it in histogram form stored in a text file, and use a histogram
object with loadFromFile().
You can also use saveToFile()that writes out the distribution collected by the histogram
object:
FILE *f = fopen("histogram.dat","w");
histogram.saveToFile( f ); // save the distribution
fclose( f );
140
OMNeT++ Manual – The Simulation Library
The cVarHistogram class can be used to create histograms with arbitrary (non-equidistant)
cells. It can operate in two modes:
• manual, where you specify cell boundaries explicitly before starting collecting
• automatic, where transform() will set up the cells after collecting a certain number of
initial observations. The cells will be set up so that as far as possible, an equal number
of observations fall into each cell (equi-probable cells).
Creating an object:
Rangemin and rangemax is chosen after collecting the num_firstvals initial observations.
One cannot add cell boundaries when the histogram has already been transformed.
The k-split algorithm is an on-line distribution estimation method. It was designed for
on-line result collection in simulation programs. The method was proposed by Varga and
Fakhamzadeh in 1997. The primary advantage of k-split is that without having to store
the observations, it gives a good estimate without requiring a-priori information about the
distribution, including the sample size. The k-split algorithm can be extended to multi-
dimensional distributions, but here we deal with the one-dimensional version only.
The algorithm
The k-split algorithm is an adaptive histogram-type estimate which maintains a good parti-
tioning by doing cell splits. We start out with a histogram range [xlo , xhi ) with k equal-sized
histogram cells with observation counts n1 , n2 , · · · nk . Each collected observation increments
the corresponding observation count. When an observation count ni reaches a split threshold,
the cell is split into k smaller, equal-sized cells with observation counts ni,1 , ni,2 , · · · ni,k ini-
tialized to zero. The ni observation count is remembered and is called the mother observation
count to the newly created cells. Further observations may cause cells to be split further (e.g.
141
OMNeT++ Manual – The Simulation Library
ni,1,1 , ...ni,1,k etc.), thus creating a k-order tree of observation counts where leaves contain live
counters that are actually incremented by new observations, and intermediate nodes contain
mother observation counts for their children. If an observation falls outside the histogram
range, the range is extended in a natural manner by inserting new level(s) at the top of the
tree. The fundamental parameter to the algorithm is the split factor k. Low values of k, k = 2
and k = 3 are to be considered. In this paper we examine only the k = 2 case.
Figure 7.6: Illustration of the k-split algorithm, k = 2. The numbers in boxes represent the
observation count values
For density estimation, the total number of observations that fell into each cell of the par-
tition has to be determined. For this purpose, mother observations in each internal node of
the tree must be distributed among its child cells and propagated up to the leaves.
Let n...,i be the (mother) observation count for a cell, s...,i be the total observation count in a
cell n...,i plus the observation counts in all its sub-, sub-sub-, etc. cells), and m...,i the mother
observations propagated to the cell. We are interested in the ñ...,i = n...,i + m...,i estimated
amount of observations in the tree nodes, especially in the leaves. In other words, if we have
ñ...,i estimated observation amount in a cell, how to divide it to obtain m...,i,1 , m...,i,2 · · · m...,i,k
that can be propagated to child cells. Naturally, m...,i,1 + m...,i,2 + · · · + m...,i,k = ñ...,i .
Two natural distribution methods are even distribution (when m...,i,1 = m...,i,2 = · · · = m...,i,k )
and proportional distribution (when m...,i,1 : m...,i,2 : · · · : m...,i,k = s...,i,1 : s...,i,2 : · · · : s...,i,k ).
Even distribution is optimal when the s...,i,j values are very small, and proportional distri-
bution is good when the s...,i,j values are large compared to m...,i,j . In practice, a linear com-
bination of them seems appropriate, where λ = 0 means even and λ = 1 means proportional
distribution:
ñ···,i s...,i,j
m···,i,j = (1 − λ) + λñ···,i , λ ∈ [0, 1] (7.1)
k s···,i
Note that while n...,i are integers, m...,i and thus ñ...,i are typically real numbers. The his-
togram estimate calculated from k-split is not exact, because the frequency counts calculated
in the above manner contain a degree of estimation themselves. This introduces a certain
cell division error; the λ parameter should be selected so that it minimizes that error. It has
been shown that the cell division error can be reduced to a more-than-acceptable small value.
Strictly speaking, the k-split algorithm is semi-online, because its needs some observations
to set up the initial histogram range. However, because of the range extension and cell split
capabilities, the algorithm is not very sensitive to the choice of the initial range, so very few
observations are enough for range estimation (say Npre = 10). Thus we can regard k-split as
an on-line method.
K-split can also be used in semi-online mode, when the algorithm is only used to create an
optimal partition from a larger number of Npre observations. When the partition has been
created, the observation counts are cleared and the Npre observations are fed into k-split once
142
OMNeT++ Manual – The Simulation Library
Figure 7.7: Density estimation from the k-split cell tree. We assume λ = 0, i.e. we distribute
mother observations evenly.
again. This way all mother (non-leaf) observation counts will be zero and the cell division
error is eliminated. It has been shown that the partition created by k-split can be better than
both the equi-distant and the equal-frequency partition.
OMNeT++ contains an experimental implementation of the k-split algorithm, the cKSplit
class. Research on k-split is still under way.
int treeDepth();
int treeDepth(sGrid& grid);
struct sGrid
{
int parent; // index of parent grid
int reldepth; // depth = (reldepth - rootgrid’s reldepth)
long total; // sum of cells & all subgrids (includes ‘mother’)
int mother; // observations ‘inherited’ from mother cell
int cells[K]; // cell values
};
143
OMNeT++ Manual – The Simulation Library
transient period. After the model has entered steady state, simulation must proceed until
enough statistical data have been collected to compute result with the required accuracy.
Detection of the end of the transient period and a certain result accuracy is supported by
OMNeT++. The user can attach transient detection and result accuracy objects to a result
object (cStatistic’s descendants). The transient detection and result accuracy objects will
do the specific algorithms on the data fed into the result object and tell if the transient period
is over or the result accuracy has been reached.
The base classes for classes implementing specific transient detection and result accuracy
detection algorithms are:
Basic usage
Attaching detection objects to a cStatistic and getting pointers to the attached objects:
addTransientDetection(cTransientDetection *object);
addAccuracyDetection(cAccuracyDetection *object);
cTransientDetection *transientDetectionObject();
cAccuracyDetection *accuracyDetectionObject();
Transient detection
Currently one transient detection algorithm is implemented, i.e. there’s one class derived
from cTransientDetection. The cTDExpandingWindows class uses the sliding window
approach with two windows, and checks the difference of the two averages to see if the tran-
sient period is over.
Accuracy detection
Currently one accuracy detection algorithm is implemented, i.e. there’s one class derived
from cAccuracyDetection. The algorithm implemented in the cADByStddev class is: di-
vide the standard deviation by the square of the number of values and check if this is small
enough.
144
OMNeT++ Manual – The Simulation Library
while (...)
{
double response_time;
//...
resp_v.record( response_time );
//...
}
All cOutVector objects write to the same, common file. The file is textual; each record()
call generates a line in the file. The output file can be processed using Plove, but otherwise
its simple format allows it to be easily processed with sed, awk, grep and the like, and it
can be imported by spreadsheet programs. The file format is described later in this manual
(in the section about simulation execution).
You can disable the output vector or specify a simulation time interval for recording either
from the ini file or directly from program code:
cOutVector v("v");
simtime_t t =...;
v.enable();
v.disable();
v.setStartTime( t );
v.setStopTime( t+100.0 );
If the output vector object is disabled or the simulation time is outside the specified interval,
record() doesn’t write anything to the output file. However, if you have a Tkenv inspector
window open for the output vector object, the values will be displayed there, regardless of
the state of the output vector object.
• to do several runs with different parameter settings/random seed and determine the
dependence of some measures on the parameter settings. For example, multiple runs
and output scalars are the way to produce Throughput vs. Offered Load plots.
145
OMNeT++ Manual – The Simulation Library
Output scalars are recorded with the recordScalar() member function. It is overloaded,
you can use it to write doubles and strings (const char *):
Calls to recordScalar() and recordStats() are usually placed in the redefined fin-
ish() member function of a simple module.
The above calls write into the (textual) output scalar file. The output scalar file is preserved
across simulation runs (unlike the output vector file is, scalar files are not deleted at the
beginning of each run). Data are always appended at the end of the file, and output from
different simulation runs are separated by special lines.
ev << "started\n";
ev << "about to send message #" << i << endl;
ev << "queue full, discarding packet\n";
The more traditional-looking but functionally equivalent ev.printf() form also exists.
The exact way messages are displayed to the user depends on the user interface. In the
command-line user interface (Cmdenv), it is simply dumped to the standard output. (This
output can also be disabled from the ini file so that it doesn’t slow down simulation when it is
not needed.) In windowing user interfaces (Tkenv), each simple module can have a separate
text output window.
The above means that you should not use printf(), cout « and the like because with
Tkenv, their output would appear in the terminal window behind the graphical window of
the simulation application.
7.10.2 Watches
You may want some of your int, long, double, char, etc. variables to be inspect-able in Tkenv
and to be output into the snapshot file. In this case, you can create cWatch objects for them
with the WATCH() macro:
146
OMNeT++ Manual – The Simulation Library
int i; WATCH(i);
char c; WATCH(c);
When you open an inspector for the simple module in Tkenv and click the Objects/Watches
tab in it, you’ll see your WATCHed variables and their values there. Tkenv also lets you
change the value of a WATCHed variable.
The WATCH() macro expands to a dynamically created cWatch object. The object remembers
the address and type of your variable. The macro expands to something like:
new cWatch("i",i);
You can also make a WATCH for pointers of type char* or cObject*, but this may cause a seg-
mentation fault if the pointer does not point to a valid location when Tkenv or snapshot()
wants to use it.
You can also set watches for variables that are members of the module class or for structure
fields:
WATCH( lapbconfig.timeout );
Placement of WATCHes
Be careful not to execute a WATCH() statement more than once, as each call would create
a new cWatch object! If you use activity(), the best place for WATCHes is the top of
the activity() function. If you use handleMessage(), place the WATCH() statement into
initialize(). WATCH() creates a dynamic cWatch object, and we do not want to create a
new object each time handleMessage() is called.
7.10.3 Snapshots
The snapshot() function outputs textual information about all or selected objects of the
simulation (including the objects created in module functions by the user) into the snapshot
file.
This will append snapshot information to the end of the snapshot file. (The snapshot file
name has an extension of .sna, default is omnetpp.sna. Actual file name can be set in the
config file.)
The snapshot file output is detailed enough to be used for debugging the simulation: by
regularly calling snapshot(), one can trace how the values of variables, objects changed
over the simulation. The arguments: label is a string that will appear in the output file; obj
is the object whose inside is of interest. By default, the whole simulation (all modules etc)
will be written out.
If you run the simulation with Tkenv, you can also create a snapshot from the menu.
An example of a snapshot file:
147
OMNeT++ Manual – The Simulation Library
================================================
|| SNAPSHOT ||
================================================
| Of object: ‘simulation’
| Label: ‘three-station token ring’
| Sim. time: 0.0576872457 ( 57ms)
| Network: ‘token’
| Run no. 1
| Started at: Mar 13, 1997, 14:23:38
| Time: Mar 13, 1997, 14:27:10
| Elapsed: 5 sec
| Initiated by: operator
================================================
148
OMNeT++ Manual – The Simulation Library
[...token.num_messages omitted...]
149
OMNeT++ Manual – The Simulation Library
150
OMNeT++ Manual – The Simulation Library
7.10.4 Breakpoints
With activity() only! In those user interfaces which support debugging, breakpoints stop
execution and the state of the simulation can be examined.
You can set a breakpoint inserting a breakpoint() call into the source:
for(;;)
{
cMessage *msg = receive();
breakpoint("before-processing");
breakpoint("before-send");
send( reply_msg, "out" );
//..
}
In user interfaces that do not support debugging, breakpoint() calls are simply ignored.
Some container classes and functions suspend the simulation and issue warning messages in
potentially bogus/dangerous situations, for example when an object is not found and NULL
pointer/reference is about to be returned. Very often this is useful, but sometimes it is more
trouble. You can turn warnings on/off from the ini file (warnings=yes/no).
It is a good practice to leave warnings enabled, and temporarily disable warnings in places
where OMNeT++ would normally issue warnings but you know the code is correct. This is
done in the following way:
bool w = simulation.warnings();
simulation.setWarnings( false );
...
... // critical code
...
simulation.setWarnings( w );
151
OMNeT++ Manual – The Simulation Library
void FooModule::finish()
{
ev << stackUsage() << "bytes of stack used\n";
}
The value includes the extra stack added by the user interface library (see extraStackforEn-
vir in envir/omnetapp.h), which is currently 8K for Cmdenv and at least 16K for Tkenv. 2
stackUsage()also works by checking the existence of predefined byte patterns in the stack
area, so it is also subject to the above effect with local variables.
setDisplayString("p=100,100;b=60,30,rect;o=red,black,3", true);
Setting appearance of a compound module when it’s displayed as a bounding box for its
submodules:
parentModule()->setDisplayStringAsParent("p=100.....", true);
The display string of a connection arrow is stored in its source gate, so you’ll need to write
something like this:
2 The actual value is dependent on the operating system, e.g. SUN Solaris needs more space.
152
OMNeT++ Manual – The Simulation Library
gate("out")->setDisplayString("o=yellow,3");
The cDisplayStringParser utility class lets you parse and manipulate display strings.
As far as cDisplayStringParser is concerned, a display string (e.g. "p=100,125;i=cloud")
is a string that consist of several tags separated by semicolons, and each tag has a name and
after an equal sign, zero or more arguments separated by commas.
The class facilitates tasks such as finding out what tags a display string has, adding new
tags, adding arguments to existing tags, removing tags or replacing arguments. The internal
storage method allows very fast operation; it will generally be faster than direct string ma-
nipulation. The class doesn’t try to interpret the display string in any way, nor does it know
the meaning of the different tags; it merely parses the string as data elements separated by
semicolons, equal signs and commas.
An example:
cDisplayStringParser dispstr("a=1,2;p=alpha,,3");
dispstr.insertTag("x");
dispstr.setTagArg("x",0,"joe");
dispstr.setTagArg("x",2,"jim");
dispstr.setTagArg("p",0,"beta");
ev << dispstr.getString(); // result: "x=joe,,jim;a=1,2;p=beta,,3"
If you plan to implement a completely new class (as opposed to subclassing something al-
ready present in OMNeT++), you have to ask yourself whether you want the new class to
be based on cObject or not. Note that we’re not saying you should always subclass from
cObject. Both solutions have advantages and disadvantages which you have to consider
individually for each class.
cObject already carries (or provides framework for) significant functionality that are ei-
ther important for your particular purpose or not. Subclassing cObject generally means
you have more code to write (as you have to redefine certain virtual functions and adopt
to conventions) and your class will be a bit more heavy-weight. In turn, it will integrate
into OMNeT++ better, for example it will be more visible in Tkenv and can be automatically
garbage-collected (this will be discussed later). If you need to store your objects in OMNeT++
objects like cQueue, or you’ll want to store OMNeT++ classes in your object, then you must
153
OMNeT++ Manual – The Simulation Library
3
subclass from cObject.
The most significant features cObject has is the name string (which has to be stored some-
where, so it has its overhead) and ownership management (see section 7.13) which also has
the advantages but also some costs.
As a general rule, small struct-like classes like IPAddress, MACAddress, RoutingTableEn-
try, TCPConnectionDescriptor, etc. are better not sublassed from cObject. On the
other hand, if you want to store your objects in OMNeT++ objects like cQueue, or you’ll want
to store OMNeT++ classes in your object, then you must subclass from cObject. If your class
fits neither category, you’ll need to see if cObject brings any benefit for you, and decide ac-
cordingly.
• Constructor. At least two constructors should be provided: one that takes the object
name string as const char * (recommended by convention), and another one with no
arguments (must be present). The two are usually implemented as a single method,
with NULL as default name string.
• Copy constructor, which must have the following signature for a class X: X(const X&).
The copy constructor is used whenever an object is duplicated. The usual implementa-
tion of the copy constructor is to initialize the base class with the name (name()) of the
other object it receives, then call the assignment operator (see below).
• Destructor. Any good-tempered class has a destructor.
• Duplication function, cObject *dup() const. It should create and return an exact
duplicate of the object. It is usually a one-line function, implemented with the help of
the new operator and the copy constructor.
• Assigment operator, that is, X& operator=(const X&) for a class X. It should copy
the contents of the other object into this one, except the name string. See later what to
do if the object contains pointers to other objects.
The following function should be implemented if your class contains via pointers or as data
member) other object subclassed from cObject.
cObject”
154
OMNeT++ Manual – The Simulation Library
• Object info, void info(char *). The info() function should print a one-line info
about object contents into the given buffer – usually the class name, the object name,
important state variables, etc. This is used when Tkenv displays list of objects (in the
object tree or in listboxes). The length of the info should not exceed 500 chars.
• Detailed object info, void writeContents(ostream&). It should write a detailed
multi-line report about the object contents into the stream provided. This is currently
only used by snapshot().
7.12.4 Details
We’ll go through the details using an example. We create a new class NewClass, redefine
all above mentioned cObject member functions, and explain the conventions, rules and
tips associated with them. To demonstrate as much as possible, the class will contain an
int data member, dynamically allocated non-cObject data (an array of doubles), an OM-
NeT++ object as data member (a cQueue), and a dynamically allocated OMNeT++ object (a
cMessage).
The class declaration is the following. It contains the declarations of all methods discussed
in the previous section.
//
// file: newclass.h
//
#include <omnetpp.h>
We’ll discuss the implementation method by method. Here’s the top of the .cc file:
155
OMNeT++ Manual – The Simulation Library
//
// file: newclass.cc
//
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include "newclass.h"
Register_Class( NewClass );
The constructor (above) calls the base class constructor with the name of the object, then
initializes its own data members. cObject-based data members should have their owners
explicitly set to NULL.
The copy constructor relies on the assignment operator. Because by convention the assign-
ment operator does not copy the name member, it is passed here to the base class constructor.
(Alternatively, we could have written setName(other.name()) into the function body.)
Note that pointer members have to be initialized (to NULL or to an allocated object/memory)
before calling the assignment operator, to avoid crashes.
cObject-based data members should have their owners explicitly set to NULL.
NewClass::~NewClass()
{
delete [] array;
if (msg->owner()==this)
delete msg;
}
The destructor should delete all data structures the object allocated. cObject-based objects
should only be deleted if they are owned by the object – details will be covered in section 7.13.
156
OMNeT++ Manual – The Simulation Library
The dup() functions is usually just one line, like the one above.
data = other.data;
queue = other.queue;
queue.setName(other.queue.name());
Complexity associated with copying and duplicating the object is centralized in the assign-
ment operator, so it is usually the one that requires the most work from you of all methods
required by cObject.
If you do not want to implement object copying and duplication, you should implement the
assigment operator to call copyNotSupported() – it’ll throw an exception that stops the
simulation with an error message if this function is called.
The assignment operator copies contents of the other object to this one, except the name
string. It should always return *this.
First, we should make sure we’re not trying to copy the object to itself, because it might be
disastrous. If so (that is, &other==this), we return immediately without doing anything.
The base class part is copied via invoking the assignment operator of the base class.
New data members are copied in the normal C++ way. If the class contains pointers, you’ll
most probably want to make a deep copy of the data where they point, and not just copy the
pointer values.
If the class contains pointers to OMNeT++ objects, you need to take ownership into account.
If the contained object is not owned then we assume it is a pointer to an “external” object,
consequently we only copy the pointer. If it is owned, we duplicate it and become the owner
of the new object. Details of ownership management will be covered in section 7.13.
void NewClass::forEach(ForeachFunc f)
{
if (f(this,true))
{
queue->forEach(f);
if (msg)
msg->forEach(f);
157
OMNeT++ Manual – The Simulation Library
}
f(this,false);
}
The foreach() function should be called for each OMNeT++ member of the class. See the
API Reference for more information of foreach().
Here you should produce a concise, one-line info about the object. You should try not to
exceed 40-80 characters, since the string will be shown in tooltips and listboxes. The length
of the buffer is 500 bytes, so in any case you should not exceed that length. You can make
use of cObject’s info() method that produces the class name and the object name.
writecontents() is expected to write values of all data members to the stream. You do
not need to include anything about contained cObject-based objects, because they will be
included via foreach().
See the virtual functions of cObject in the class library reference for more information. The
sources of the Sim library (include/, src/sim/) can serve as further examples.
158
OMNeT++ Manual – The Simulation Library
From an object you can navigate to its owner by calling the owner() method, defined in
cObject. The other direction, enumerating the objects owned can be done via foreach()
that loops through all contained objects and checking the owner of each object.
7.13.2 Purpose
The purpose of maintaining the ownership tree is threefold:
• to provide a certain degree of garbage collection (that is, automatic deletion of objects
that are no longer needed)
• to prevent a certain types of programming errors, namely, those associated with wrong
ownership handling.
• it provides some “reflection” (in the Java sense), which enables Tkenv to display which
objects are present (and where) in the simulation, to find “lost” (leaked) objects, etc.
Some examples of programming errors that can be caught by the ownership facility:
• attempts to send a message while it’s still in a queue, encapsulated in another message,
etc.
• attempts to send/schedule a message while it’s still owned by the simulation kernel (i.e.
scheduled as a future event)
• attempts to send the very same message object to multiple destinations at the same
time (ie. to all connected modules)
The above errors are easy to make in the code, and if not detected automatically, they could
cause random crashes which are usually very difficult to track down.
Of course, some errors of the same kind still cannot be detected automatically, like calling
member functions of a message object which has been sent to (and so currently kept by)
another module.
Ownership is usually established and managed automatically. It is not hard to guess that
objects (i.e. messages) inserted into a cQueue or a cArray will be owned by that object (by
4 Note that it’s not necessary for a container object like a queue to actually own all inserted objects. This behavior
159
OMNeT++ Manual – The Simulation Library
default – this can be changed, as described later). Messages encapsulated into other mes-
sages (by cMessages’s encapsulate() method), and cPar’s added to a message will also
become owned by the message object (again, this can be changed), and they are deallocated
automatically when the message object is deleted.
But objects which, not being stored in another object, appear not to have owners usually
have one as well. If you just create a message object inside a simple module (e.g. from
activity(), handleMessage() or any function called from them), it will be owned by
simple module, or more precisely, by its “local objects list” (a member of cSimpleModule).
So the following line:
actually creates the message object and automatically adds it to the module’s local objects
list. 5
The local objects list also plays a role when an object is removed from a container object
(e.g. when a message is removed from a queue). In that case, the container object “drops” the
ownership of the object, and the object will “join” its default owner, the local objects list of the
simple module (again, to the currently active simple module’s list). Thus, an innocent-looking
statement actually involves a transfer of ownership of the message object from the queue to
the simple module. The same thing happens when a message is decapsulated from another
message, when cPar’s are removed from a cArray, and in many more cases.
Sanity checks
The send() and scheduleAt() functions make use of the ownership mechanism to do some
sanity check: the message being sent/scheduled must be owned by module’s local objects list.
If it is not, then it’s an error, because then the message is probably with another module (i.e.
already sent), or currently scheduled, or inside a queue, a message or some other object – in
either case, you do not have any authority to send it. When you get this error message ("not
owner of object"), you might feel tempted to forcibly take ownership of the message ob-
ject by means of setOwner(). Note that it would be entirely wrong, and would probably lead
to crash further on in your program. Do not use setOwner()! Instead, you need to carefully
examine who has the ownership of the message, why’s that, and then probably you’ll need to
fix the logic somewhere in your program.
For completeness, it should also be mentioned that class members of a simple module are
collected on a “class members list”. The reason for the existence of this list is not so much
garbage collection or sanity checks, but rather assisting Tkenv in displaying the class mem-
bers list in simple module inspectors.
5 More precisely: to the currently executing module’s local object list, because that’s the best guess a cMessage
160
OMNeT++ Manual – The Simulation Library
The local objects list is also the reason why you rarely need to put delete statements in your
simple module destructors.
When you restart the simulation in Tkenv or begin a new run in Cmdenv, OMNeT++ has
to clean up the previously running simulation. This involves (a) deleting all modules in
the network, and (b) deleting all messages in the Future Events Set. Modules (both simple
and compound) can also be dynamically deleted during simulation (deleting a compound
module just consists of recursively deleting all submodules). At that time, one expects all
dynamically allocated objects to be properly destructed and memory released, which is not
trivial since the simulation kernel does not know what objects have been created by simple
modules. Here’s how it is done in the simulation kernel.
When a simple module gets deleted, the local objects list is also deleted in addition to the
module’s gates and parameters. This means that all objects on the module’s local objects list
(i.e. objects you allocated and need to be garbage collected) will also be deleted, and this is
exactly what we need as garbage collection.
The result is that as long as you only have dynamically allocated memory as (or within) cOb-
ject-based objects, you don’t have to worry about writing module destructors: everything is
taken care of automatically.
Note that this garbage collection can nicely co-exist with module destructors you write. If you
delete an object explicitly, it is redundant but does no harm: its destructor will also remove
it from the owner’s list (which might be the module’s local object list), so double deletion will
not occur.
MyModule::~MyModule()
{
delete timeoutmsg; // redundant but does no harm
}
Other allocated memory (e.g. C++ arrays of integers, doubles, structs or pointers) or ob-
jects which have nothing to do with cObject (e.g. STL objects or your non-cObject rooted
classes) are invisible to the ownership mechanism discussed here, and must be deleted in the
destructor in the conventional way.
161
OMNeT++ Manual – The Simulation Library
MyModule::~MyModule()
{
delete [] distances; // OMNeT++ knows nothing about this vector,
// so we need to clean up it manually
}
It is similar when you have arrays of cMessage pointers (or in general any other non-OM-
NeT++ data structure which holds pointers to cObject-rooted objects). Then it is enough
if you delete the array or the data structure, the objects will be cleaned up via the garbage
collection mechanism.
MyModule::~MyModule()
{
delete [] events; // we need to delete only the pointer array itself,
// deleting the message objects can be left to
// the garbage collection mechanism
}
In any case, remember not to put any destructor-like code inside the module’s finish()
function. The main reason is that whenever your simulation stops with an error (or you just
stop it within Tkenv), the finish() functions will not be called and thus, memory will be
leaked.
Can it crash?
A potential crash scenario is when the object ownership mechanism deletes objects before
your code does, and your code, not aware of the ownership mechanism and not knowing that
the objects have already been deleted, tries to delete them again. Note that this cannot hap-
pen as long as objects stay within the module, because the garbage collection mechanism is
embedded deeply in the base class of your simple module, thus it is guaranteed by C++ lan-
guage rules to take place after all your destructor-related code (your simple module class’s
destructor and the destructors of data members you added to the simple module class) have
executed.
However, if some of your objects have been sent to other modules (e.g. inside a message) while
their ownership stayed with the original module (which is a situation one should not allow to
happen), the above order of destruction is not guaranteed and crash is possible. To produce
the above crash, however, one must work hard to add a nonstandard way of storing objects
in a message. This situation will be discussed later in more detail, after we’ve discussed how
containers like cQueue and cArray work.
Another interesting aspect is what happens when an activity() simple module is deleted.
Objects that were local variables of activity() are just left on the coroutine stack. They
162
OMNeT++ Manual – The Simulation Library
themselves need not (and must not) be deleted using the delete operator, but they need
to be properly destructed (their destructors called) so that the memory they allocated can be
released. As of OMNeT++ version 2.3, this is done by actually calling the method named dis-
card() in the cObject destructor instead of the directly the delete operator. discard()
invokes either the delete operator (if the object was allocated dynamically) or directly the
object’s destructor (if the object was a local variable in activity() or a function called
from activity()). In future releases, the implementation might be changed to rely on C++
exceptions (stack unwinding) for proper cleanup.
Insertion
cArray and cQueue have internal data structures (array and linked list) to store the objects
which are inserted into them. However, they do not necessarily own all of these objects.
(Whether they own an object or not can be determined from that object’s owner() pointer.)
The default behaviour of cQueue and cArray is to take ownership of the objects inserted.
This behavior can be changed via the takeOwnership flag. The flag is part of cObject so that
every container object can make use of it, and can be get/set via the takeOwnership() and
setTakeOwnership() methods.
Here’s what the insert operation of cQueue (or cArray) does:
• if the takeOwnership flag is true, take ownership of the object, otherwise just leave it
with its original owner
Removal
Here’s what the remove family of operations in cQueue (or cArray) does:
163
OMNeT++ Manual – The Simulation Library
• if the object is actually owned by this cQueue/cArray, release ownership of the object,
otherwise just leave it with its current owner
After the object was removed from a cQueue/cArray, you may further use it, or if it’s not
needed any more, you can delete it.
The release ownership phrase requires further explanation. When you remove and object
from a queue or array, the ownership is expected to be transferred to the simple module’s
local objects list. This is acomplished by the drop() function, which transfers the ownership
to the object’s default owner. defaultOwner() is a virtual method returning cObject*
defined in cObject, and its implementation returns the currently executing simple module’s
local object list.
6
As an example, the remove() method of cQueue is implemented like this:
return obj;
}
Destructor
The destructor should delete all data structures the object allocated. From the contained
objects, only the owned ones are deleted – that is, where obj->owner()==this.
Object copying
The ownership mechanism also has to be taken into consideration when a cArray or cQueue
object is duplicated. The duplicate is supposed to have the same content as the original,
however the question is whether the contained objects should also be duplicated or just their
pointers taken over to the duplicate cArray or cQueue.
The convention followed by cArray/cQueue is that only owned objects are copied, and the
contained but not owned ones will have their pointers taken over and their original owners
left unchanged.
In fact, the same question arises at three places: the assignment operator operator=(),
the copy constructor and the dup() method. In OMNeT++, the convention is that copying is
implemented in the assignment operator, and the other two just rely on it. (The copy con-
structor just constructs an empty object and invokes assigment, while dup() is implemented
as new cArray(*this)).
164
OMNeT++ Manual – The Simulation Library
7
destructor that deletes all owned objects.
This behaviour will probably be changed in the next major release, and every class will be
made responsible for deleting its own owned objects. This will be closer to the usual C++
practice and will make the OMNeT++ simulation library easier to understand. Also, it will be
more efficient with both memory and execution time, without losing significant functionality.
The change will be transparent to simulations, unless you implemented a container class
which relies on cObject’s destructor to destroy owned objects.
As of the 2.3 release, cObject contains 4 pointers. ownerp points to the owner, firstchildp
points to the first owned object, while prevp, nextp are used to build a doubly linked list of
objects held by the same owner. These pointers are private data members, they cannot be
accessed directly, only via certain member functions. Changing the owner of an object (se-
tOwner() method in cObject) involves about 8-9 pointer assignments (i.e., ownerp, prevp,
nextp and firstchildp in the object, in its owner and siblings).
This data structure is likely to change: firstchildp, prevp and nextp will be removed from
cObject, and only ownerp will remain. The setOwner() method will probably be removed
entirely.
7 This is also the reason why there are currently so few delete calls in the simulation kernel sources: container
classes like cArray or cQueue leave the task of deleting the contained objects they own to the cObject destructor.
165
OMNeT++ Manual – The Simulation Library
166
OMNeT++ Manual – Building Simulation Programs
Chapter 8
8.1 Overview
As it was already mentioned, an OMNeT++ model physically consists of the following parts:
• NED language topology description(s). These are files with the .ned suffix.
• Simple modules implementations and other C++ code, in .cc files (or .cpp, on Win-
dows)
To build an executable simulation program, first you need to translate the NED files and the
message files into C++, using the NED compiler (nedc) and the message compiler (opp_msgc).
After this step, the process is the same as building any C/C++ program from source: all C++
sources need to be compiled into object files (.o files on Unix/Linux, and .obj on Windows),
and all object files need to be linked with the necessary libraries to get an executable.
File names for libraries differ for Unix/Linux and for Windows, and it’s also different for
static and shared libraries. Suppose you have a library called Tkenv. On a Unix/Linux sys-
tem, the file name for the static library would be something like libtkenv.a (or libtkenv.a.<version>),
and the shared library would be called libtkenv.so (or libtkenv.so.<version>). The
Windows version of the static library would be tkenv.lib, and the DLL (which is the Win-
dows equivalent of shared libraries) would be a file named tkenv.dll.
You’ll need to link with the following libraries:
• The simulation kernel and class library, called sim_std (file libsim_std.a, sim_std.lib,
etc).
• User interfaces. The common part of all user interfaces is the envir library (file liben-
vir.a, etc), and the specific user interfaces are tkenv and cmdenv (libtkenv.a, libcm-
denv.a, etc). You have to link with envir, plus either tkenv or cmdenv.
Luckily, you do not have to worry about the above details, because automatic tools like
opp_makemake will take care of the hard part for you.
The following figure gives an overview of the process of building and running simulation
programs.
This section discusses how to use the simulation system on the following platforms:
167
OMNeT++ Manual – Building Simulation Programs
168
OMNeT++ Manual – Building Simulation Programs
8.2.1 Installation
The installation process depends on what distribution you take (source, precompiled RPM,
etc.) and it may change from release to release, so it is better to refer to the readme files. If
you compile from source, you can expect the usual GNU procedure: ./configure followed
by make.
% opp_makemake -h
Once you have the source files (*.ned, *.msg, *.cc, *.h) in a directory, cd there then type:
% opp_makemake
This will create a file named Makefile. Thus if you simply type make, your simulation
program should build. The name of the executable will be the same as the name of the
directory containing the files.
The freshly generated Makefile doesn’t contain dependencies, it is advisable to add them
by typing make depend. The warnings during the dependency generation process can be
safely ignored.
In addition to the simulation executable, the Makefile contains other targets, too. Here is
a list of important ones:
Target Action
The default target is to build the simulation exe-
cutable
depend Adds (or refreshes) dependencies in the Make-
file
clean Deletes all files that were produced by the make
process
169
OMNeT++ Manual – Building Simulation Programs
If you already had a Makefile in that directory, opp_makemake will refuse overwriting it.
You can force overwriting the old Makefile with the -f option:
% opp_makemake -f
If you have problems, check the path definitions (locations of include files and libraries etc.)
in the configure script and correct them if necessary. Then re-run configure to commit the
changes to all makefiles, the opp_makemake script etc.
You can specify the user interface (Cmdenv/Tkenv) with the -u option (with no -u, Tkenv is
the default):
% opp_makemake -u Tkenv
Or:
% opp_makemake -u Cmdenv
The name of the output file is set with the -o option (the default is the name of the directory):
% opp_makemake -o fddi-net
If some of your source files are generated from other files (for example, you use machine-
generated NED files), write your make rules into a file called makefrag. When you run
opp_makemake, it will automatically insert makefrag into the resulting makefile. With
the -i option, you can also name other files to be included into Makefile.
If you want better portability for your models, you can generate Makefile.in instead of
Makefile with opp_makemake’s -m option. You can then use autoconf-like configure
scripts to generate the Makefile.
opp_makemake -n
170
OMNeT++ Manual – Building Simulation Programs
This results in recursive makefiles: when you build the simulation, make will descend into
app/ and routing/, run make in both, then it will link an executable with the object files
in the two directories.
You may need to use the -I option if you include files from other directories. The -I option is
for both C++ and NED files. In our example, you could run
opp_makemake -n -I../routing
build_shared_libs=yes
build_shared_libs=no
Then you have to re-run the configure script and rebuild everything:
./configure
make clean
make
8.3.1 Installation
It is easiest to start with the binary, installer version. It contains all necessary software
except MSVC, and you can get a working system up and running very fast.
171
OMNeT++ Manual – Building Simulation Programs
Later you’ll probably want to download and build the source distribution too. Reasons for
that might be to compile the libraries with different flags, to debug into them, or to recompile
with support for additional packages (e.g. Akaroa, MPI). Compilation should be painless (it
takes a single nmake -f Makefile.vc command) after you get the different component
directories right in configuser.vc. Additional software needed for the compilation is also
described in doc/.
opp_nmakemake
opp_nmakemake has several command-line options, mostly the same as the Unix version.
Then you can build the program by typing:
nmake -f Makefile.vc
The most common problem is that nmake (which is is part of MSVC) cannot be found because
it is not in the path. You can fix this by running vcvars32.bat, which can be found in the
MSVC bin directory (usually C:\Program Files\Microsoft Visual Studio\VC98\Bin).
• how to get the graphical environment. By default, the sample simulations link
with Cmdenv if you rebuild them from the IDE. To change to Tkenv, choose Build|Set
active configuration from the menu, select “Debug-Tkenv” or “Release-Tkenv”, then re-
link the executable.
• can’t find a usable init.tcl. If you get this message, Tcl/Tk is missing the TCL_LIBRARY
environment variable which is normally set by the installer. If you see this message,
you need to set this variable yourself to the Tcl lib/ directory.
• changed compiler settings. Changes since OMNeT++ 2.2: You’ll need exception han-
dling and RTTI turned ON, and stack size set to as low as 64K. See the readme file for
rationale and more hints.
• adding NED files. After you added a .ned file to the project, you also have to add a
_n.cpp file, and set a Custom Build Step for them:
172
OMNeT++ Manual – Building Simulation Programs
173
OMNeT++ Manual – Building Simulation Programs
174
OMNeT++ Manual – Running The Simulation
Chapter 9
You would typically test and debug your simulation under Tkenv, then run actual simulation
experiments from the command line or shell script, using Cmdenv. Tkenv is also better
suited for educational or demonstration purposes.
Both Tkenv and Cmdenv are provided in the form of a library, and you choose between them
by linking one or the other into your simulation executable. (Creating the executable was
described in chapter 8). Both user interfaces are supported on Unix and Windows platforms.
Common functionality in Tkenv and Cmdenv has been collected and placed into the Envir
library, which can be thought of as the “common base class” for the two user interfaces.
The user interface is separated from the simulation kernel, and the two parts interact
through a well-defined interface. This also means that, if needed, you can write your own
user interface or embed an OMNeT++ simulation into your application without any change
to models or the simulation library.
Configuration and input data for the simulation are described in a configuration file usually
called omnetpp.ini. Some entries in this file apply to Tkenv or Cmdenv only, other set-
tings are in effect regardless of the user interface. Both user interfaces accept command-line
arguments, too.
The following sections explain omnetpp.ini and the common part of the user interfaces,
describe Cmdenv and Tkenv in detail, then go on to specific problems.
9.2.1 An example
For a start, let us see a simple omnetpp.ini file which can be used to run the Fifo1 sample
simulation under Cmdenv.
175
OMNeT++ Manual – Running The Simulation
[General]
network = fifonet1
sim-time-limit = 500000s
output-vector-file = fifo1.vec
[Cmdenv]
express-mode = yes
[Parameters]
# generate a large number of jobs of length 5..10 according to Poisson
fifonet1.gen.num_messages = 10000000
fifonet1.gen.ia_time = exponential(1)
fifonet1.gen.msg_length = intuniform(5,10)
# processing speeed of queue server
fifonet1.fifo.bits_per_sec = 10
The file is grouped into sections named [General], [Cmdenv] and [Parameters], each one
containing several entries. The [General] section applies to both Tkenv and Cmdenv, and
the entries in this case specify that the network named fifonet1 should be simulated and
run for 500,000 simulated seconds, and vector results should be written into the fifo1.vec
file. The entry in the [Cmdenv] section tells Cmdenv to run the simulation at full speed
and print periodic updates about the progress of the simulation. The [Parameters] section
assigns values to parameters that did not get a value (or got input value) inside the NED
files.
Lines that start with “#” or “;” are comments.
When you build the Fifo1 sample with Cmdenv and you run it by typing fifo1 (or on Unix,
./fifo1) on the command prompt, you should see something like this.
176
OMNeT++ Manual – Running The Simulation
As Cmdenv runs the simulation, periodically it prints the sequence number of the current
event, the simulation time, the elapsed (real) time, and the performance of the simulation
(how many events are processed per second; the first two values are 0 because there wasn’t
enough data for it to calculate yet). At the end of the simulation, the finish() methods
of the simple modules are run, and the output from them are displayed. On my machine
this run took 34 seconds. This Cmdenv output can be customized via omnetpp.ini entries.
The output file fifo1.vec contains vector data recorded during simulation (here, queueing
times), and it can be processed using Plove or other tools.
[General]
# this is a comment
foo="this is a single value \
for the foo parameter"
# omnetpp.ini
...
177
OMNeT++ Manual – Running The Simulation
include parameters.ini
include per-run-pars.ini
...
9.2.5 Sections
The following sections can exist:
Section Description
[General] Contains general settings that apply to all simulation runs
and all user interfaces. For details, see section 9.2.6.
[Run 1], [Run 2], ... Contains per-run settings. These sections may contain any
entries that are accepted in other sections.
[Cmdenv] Contains Cmdenv-specific settings. For details, see section
9.3.2
[Tkenv] Contains Tkenv-specific settings. For details, see section 9.4.2
[Parameters] Contains values for module parameters that did not get a
value (or got input value) inside the NED files. For details,
see section 9.5.1
[OutVectors] Configures recording of output vectors. You can specify filter-
ing by vector names and by simulation time (start/stop record-
ing). For details, see section 9.5.2
[DisplayStrings] Module display strings for Tkenv. For details, see section 9.5.3
• The ini-warnings option can be used for “debugging” ini files: if enabled, it lists
which options were searched for but not found.
• The network option selects the model to be set up and run.
• The length of the simulation can be set with the sim-time-limit and the cpu-time-
limit options (the usual time units such as ms, s, m, h, etc. can be used).
• The output file names can be set with the following options: output-vector-file,
output-scalar-file and snapshot-file.
The full list of supported options follows. Almost every one these options can also be put into
the [Run n] sections. Per-run settings have priority over globally set ones.
178
OMNeT++ Manual – Running The Simulation
179
OMNeT++ Manual – Running The Simulation
-h The program prints a short help message and the networks contained in
the executable, then exits.
-f <fileName> Specify the name of the configuration file. The default is omnetpp.ini.
Multiple -f switches can be given; this allows you to partition your con-
figuration file. For example, one file can contain your general settings,
another one most of the module parameters, another one the module pa-
rameters you change often.
-l <fileName> Load a shared object (.so file on Unix). Multiple -l switches are accepted.
Your .so files may contain module code etc. By dynamically loading all
simple module code and compiled network description (_n.o files on Unix)
you can even eliminate the need to re-link the simulation program after
each change in a source file. (Shared objects can be created with gcc -
shared...)
-r <runs> It specifies which runs should be executed (e.g. -r 2,4,6-8). This option
overrides the runs-to-execute= option in the [Cmdenv] section of the
ini file (see later).
% ./fddi -h
Available networks:
FDDI1
NRing
TUBw
TUBs
Available modules:
FDDI_MAC
FDDI_MAC4Ring
...
Available channels:
180
OMNeT++ Manual – Running The Simulation
181
OMNeT++ Manual – Running The Simulation
...
** Event #250000 T=123.74354 ( 2m 3s) Elapsed: 0m 12s
Speed: ev/sec=19731.6 simsec/sec=9.80713 ev/simsec=2011.97
Messages: created: 55532 present: 6553 in FES: 8
** Event #300000 T=148.55496 ( 2m 28s) Elapsed: 0m 15s
Speed: ev/sec=19584.8 simsec/sec=9.64698 ev/simsec=2030.15
Messages: created: 66605 present: 7815 in FES: 7
...
The first line of the status display (beginning with **) contains:
• the elapsed time (wall clock time) since the beginning of the simulation run.
• ev/sec indicates performance: how many events are processed in one real-time sec-
ond. On one hand it depends on your hardware (faster CPUs process more events per
second), and on the other hand it depends on the complexity (amount of calculations)
associated with processing one event. For example, protocol simulations tend to require
more processing per event than e.g. queueing networks, thus the latter produce higher
ev/sec values. In any case, this value is independent of the size (number of modules) in
your model.
• simsec/sec shows relative speed of the simulation, that is, how fast the simulation
is progressing compared to real time, how many simulated seconds can be done in one
real second. This value virtuall depends on everything: on the hardware, on the size
of the simulation model, on the complexity of events, and the average simulation time
between events as well.
• ev/simsec is the event density: how many events are there per simulated second.
Event density only depends on the simulation model, regardless of the hardware used
to simulate it: in a cell-level ATM simulation you’ll have very hight values (109 ), while
in a bank teller simulation this value is probably well under 1. It also depends on the
size of your model: if you double the number of modules in your model, you can expect
the event density double, too.
The third line displays the number of messages, and it is important because it may indicate
the ‘health’ of your simulation.
• Created: total number of message objects created since the beginning of the simulation
run. This does not mean that this many message object actually exist, because some
(many) of them may have been deleted since then. It also does not mean that you
created all those messages – the simulation kernel also creates messages for its own
use (e.g. to implement wait() in an activity() simple module).
182
OMNeT++ Manual – Running The Simulation
• Present: the number of message objects currently present in the simulation model,
that is, the number of messages created (see above) minus the number of messages
already deleted. This number includes the messages in the FES.
• In FES: the number of messages currently scheduled in the Future Event Set.
The second value, the number of messages present is more useful than perhaps one would
initially think. It can indicator of the ‘health’ of the simulation: if it is growing steadily, then
either you have a memory leak and losing messages (which indicates a programming error),
or the network you simulate is overloaded and queues are steadily filling up (which might
indicate wrong input parameters).
Of course, if the number of messages does not increase, it does not mean that you do not have
a memory leak (other memory leaks are also possible). Nevertheless the value is still useful,
because by far the most common way of leaking memory in a simulation is by not deleting
messages.
Tkenv is a portable graphical windowing user interface. Tkenv supports interactive execu-
tion of the simulation, tracing and debugging. Tkenv is recommended in the development
stage of a simulation or for presentation and educational purposes, since it allows one to
get a detailed picture of the state of simulation at any point of execution and to follow what
happens inside the network. The most important feaures are:
• graphical display of statistics (histograms etc.) and output vectors during simulation
execution
• labeled breakpoints
• inspector windows to examine and alter objects and variables in the model
Tkenv makes it possible to view simulation results (output vectors etc.) during execution.
Results can be displayed as histograms and time-series diagrams. This can speed up the
process of verifying the correct operation of the simulation program and provides a good
environment for experimenting with the model during execution. When used together with
gdb or xxgdb, Tkenv can speed up debugging a lot.
Tkenv is built with Tcl/Tk, and it work on all platforms where Tcl/Tk has been ported to:
Unix/X, Windows, Macintosh. You can get more information about Tcl/Tk on the Web pages
listed in the Reference.
183
OMNeT++ Manual – Running The Simulation
-h The program prints a short help message and the networks contained in
the executable, then exits.
-f <fileName> Specify the name of the configuration file. The default is omnetpp.ini.
Multiple -f switches can be given; this allows you to partition your con-
figuration file. For example, one file can contain your general settings,
another one most of the module parameters, another one the module pa-
rameters you change often.
-l <fileName> Load a shared object (.so file on Unix). Multiple -l switches are accepted.
Your .so files may contain module code etc. By dynamically loading all
simple module code and compiled network description (_n.o files on Unix)
you can even eliminate the need to re-link the simulation program after
each change in a source file. (Shared objects can be created with gcc -
shared...)
184
OMNeT++ Manual – Running The Simulation
• Step
• Run
• Fast run
• Express run
Inspectors
In Tkenv, objects can be viewed through inspectors. To start, choose Inspect|Network from
the menu. Usage should be obvious; just use double-clicks and popup menus that are brought
up by right-clicking. In Step, Run and Fast Run modes, inspectors are updated automatically
as the simulation progresses. To make ordinary variables (int, double, char etc.) appear in
Tkenv, use the WATCH() macro in the C++ code.
Tkenv inspectors also display the object pointer, and can also copy the pointer value to the
clipboard. This can be invaluable for debugging: when the simulation is running under a
debugger like gdb or the MSVC IDE, you can paste the object pointer into the debugger and
have closer look at the data structures.
Configuring Tkenv
185
OMNeT++ Manual – Running The Simulation
A significant part of Tkenv is written in Tcl, in several .tcl script files. The default location
of the scripts is passed compile-time to tkapp.cc, and it can be overridden at run-time by the
OMNETPP_TKENV_DIR environment variable. The existence of a separate script library can
be inconvenient if you want to carry standalone simulation executables to different machines.
To solve the problem, there is a possibility to compile the script parts into Tkenv.
The details: the tcl2c program (its C source is there in the Tkenv directory) is used to
translate the .tcl files into C code (tclcode.cc), which gets included into tkapp.cc. This
possibility is built into the makefiles and can be optionally enabled.
9.4.4 In Memoriam. . .
There used to be other windowing user interfaces which have been removed from the distri-
bution:
• TVEnv. A Turbo Vision-based user interface, the first interactive UI for OMNeT++.
Turbo Vision was an excellent character-graphical windowing environment, originally
shipped with Borland C++ 3.1.
• XEnv. A GUI written in pure X/Motif. It was an experiment, written before I stumbled
into Tcl/Tk and discovered its immense productivity in GUI building. XEnv never got
too far because it was really very-very slow to program in Motif. . .
# omnetpp.ini
[Parameters]
token.num_stations = 3
token.num_messages = 10000
[Run 1]
token.stations[*].wait_time = 10ms
[Run 2]
token.stations[0].wait_time = 5ms
token.stations[*].wait_time = 1000ms
186
OMNeT++ Manual – Running The Simulation
module-pathname.objectname.enabled = yes/no
module-pathname.objectname.interval = start..stop
module-pathname.objectname.interval = ..stop
module-pathname.objectname.interval = start..
The object name is the string passed to cOutVector in its constructor or with the set-
Name() member function.
Start and stop values can be any time specification accepted in NED and config files (e.g. 10h
30m 45.2s).
As with parameter names, wildcards are allowed in the object names and module path
names.
An example:
#
# omnetpp.ini
#
[OutVectors]
*.interval = 1s..60s
*.End-to-End Delay.enabled = yes
*.Router2.*.enabled = yes
*.enabled = no
The above configuration limits collection of all output vectors to the 1s..60s interval, and
disables collection of output vectors except all end-to-end delays and the ones in any module
called Router2.
187
OMNeT++ Manual – Running The Simulation
moduletype = "..."
moduletype.submodulename = "..."
moduletype.inputgatename = "..."
moduletype.submodulename.outputgatename = "..."
As with parameter names, wildcards are allowed in module types, submodule and gate
names.
If you decide for automatic seed selection, do not specify any seed value in the ini file. For
the random number generators, OMNeT++ will automatically select seeds that are 1,000,000
values apart in the sequence. If you have several runs, each run is started with a fresh set
of seeds that are 1,000,000 values apart from the seeds used for previous runs. Since the
generation of new seed values is costly, OMNeT++ has a table of pre-calculated seeds (256
values); if they are all used up, OMNeT++ starts from the beginning of the table again.
Warning! Be aware that each time the simulation program is started, OMNeT++ starts
assigning seeds from the beginning of the table. That is, if you execute Run 1, Run 2, Run 3
one at a time (e.g. from a shell script, using the Cmdenv -r command-line flag), all your runs
will be executed using the same seeds! This behavior is almost surely not what you want,
and it will be fixed in future versions of OMNeT++. Until then, it is a good idea to stick to
manually generating seeds and explicitly adding them to the ini file.
Automatic seed selection may not be appropriate for you for several reasons. First, you may
need more than 256 seeds values; or, if you use variance reduction techniques, you may want
to use the same seeds for several simulation runs. In this case, there is a standalone program
to generate appropriate seed values (seedtool will be discussed in Section 9.6), and you can
specify the seeds explicitly in the ini file.
The following ini file explicitly initializes two of the random number generators, and uses
different seed values for each run:
[Run 1]
gen0-seed = 1768507984
gen1-seed = 33648008
[Run 2]
gen0-seed = 1082809519
gen1-seed = 703931312
...
If you want the same seed values for all runs, you will write something like this:
188
OMNeT++ Manual – Running The Simulation
[General]
gen0-seed = 1768507984
gen1-seed = 33648008
All other random number generators (2,3,...) will have their seeds automatically assigned.
As a third way, you can also set the seed values from the code of a simple module using
genk_randseed(), but I see no reason why you would want to do so.
The last two options, p and t were used internally to generate a hash table of pre-computed
seeds that greatly speeds up the tool. For practical use, the g option is the most important.
Suppose you have 4 simulation runs that need two independent random number generators
each and you want to start their seeds at least 10,000,000 values apart. The first seed value
can be simply 1. You would type the following command:
The program outputs 8 numbers that can be used as random number seeds:
1768507984
33648008
1082809519
703931312
1856610745
784675296
426676692
1100642647
189
OMNeT++ Manual – Running The Simulation
run the same model with the same parameter settings but with different random number
generator seeds, to achieve statistically more reliable results.
Running a simulation several times by hand can easily become tedious, and then a good
solution is to write a control script that takes care of the task automatically. Unix shell
is a natural language choice to write the control script in, but other languages like Perl,
Matlab/Octave, Tcl, Ruby might also have justification for this purpose.
The next sections are only for Unix users. We’ll use the Unix ‘Bourne’ shell (sh, bash) to
write the control script. If you’d prefer Matlab/Octave, the contrib/octave/ directory
contains example scripts (contributed by Richard Lyon).
In simple cases, you may define all simulation runs needed in the [Run 1], [Run 2], etc.
sections of omnetpp.ini, and invoke your simulation with the -r flag each time. The -f flag
lets you use a file name different from omnetpp.ini.
The following script executes a simulation named wireless several times, with parameters
for the different runs given in the runs.ini file.
#! /bin/sh
./wireless -f runs.ini -r 1
./wireless -f runs.ini -r 2
./wireless -f runs.ini -r 3
./wireless -f runs.ini -r 4
...
./wireless -f runs.ini -r 10
To run the above script, type it in a text file called e.g. run, give it x (executable) permission
using chmod, then you can execute it by typing ./run:
% chmod +x run
% ./run
You can simplify the above script by using a for loop. In the example below, the variable
i iterates through the values of list given after the in keyword. It is very practical, since
you can leave out or add runs, or change the order of runs by simply editing the list – to
demonstrate this, we skip run 6, and include run 15 instead.
#! /bin/sh
for i in 3 2 1 4 5 7 15 8 9 10; do
./wireless -f runs.ini -r $i
done
#! /bin/sh
for ((i=1; $i<50; i++)); do
./wireless -f runs.ini -r $i
done
190
OMNeT++ Manual – Running The Simulation
[General]
network = Wireless
[Parameters]
Wireless.n = 10
... # other fixed parameters
include params.ini # include variable part
And have the following as control script. It uses two nested loops to explore all possible com-
binations of the alpha and beta parameters. Note that params.ini is created by redirecting
the echo output into file, using the > and » operators.
#! /bin/sh
for alpha in 1 2 5 10 20 50; do
for beta in 0.1 0.2 0.3 0.4 0.5; do
echo "Wireless.alpha=$alpha" > params.ini
echo "Wireless.beta=$beta" >> params.ini
./wireless
done
done
As a heavy-weight example, here’s the “runall” script of Joel Sherrill’s File System Simu-
lator. It also demonstrates that loops can iterate over string values too, not just numbers.
(omnetpp.ini includes the generated algorithms.ini.)
Note that instead of redirecting every echo command to file, they are grouped using paren-
theses, and redirected together. The net effect is the same, but you can spare some typing
this way.
#! /bin/bash
#
# This script runs multiple variations of the file system simulator.
#
all_cache_managers="NoCache FIFOCache LRUCache PriorityLRUCache..."
all_schedulers="FIFOScheduler SSTFScheduler CScanScheduler..."
for c in ${all_cache_managers}; do
for s in ${all_schedulers}; do
(
echo "[Parameters]"
echo "filesystem.generator_type = \"GenerateFromFile\""
echo "filesystem.iolibrary_type = \"PassThroughIOLibrary\""
echo "filesystem.syscalliface_type = \"PassThroughSysCallIface\""
echo "filesystem.filesystem_type = \"PassThroughFileSystem\""
echo "filesystem.cache_type = \"${c}\""
echo "filesystem.blocktranslator_type = \"NoTranslation\""
191
OMNeT++ Manual – Running The Simulation
./filesystem
done
done
#! /bin/sh
seedtool g 1 10000000 500 > seeds.txt
for seed in ‘cat seeds.txt‘; do
(
echo "[General]"
echo "random-seed = ${seed}"
echo "output-vector-file = xcube-${seed}.vec"
) > parameters.ini
./xcube
done
9.8.1 Introduction
Typical simulations are Monte-Carlo simulations: they use (pseudo-)random numbers to
drive the simulation model. For the simulation to produce statistically reliable results, one
has to carefully consider the following:
• When is the initial transient over, when can we start collecting data? We usually do not
want to include the initial transient when the simulation is still “warming up.”
• When can we stop the simulation? We want to wait long enough so that the statistics
we are collecting can “stabilize”, can reach the required sample size to be statistically
trustable.
Neither questions are trivial to answer. One might just suggest to wait “very long” or “long
enough”. However, this is neither simple (how do you know what is “long enough”?) nor
practical (even with today’s high speed processors simulations of modest complexity can take
hours, and one may not afford multiplying runtimes by, say, 10, “just to be safe.”) If you need
further convincing, please read [PJL02] and be horrified.
A possible solution is to look at the statistics while the simulation is running, and decide at
runtime when enough data have been collected for the results to have reached the required
192
OMNeT++ Manual – Running The Simulation
accuracy. One possible criterion is given by the confidence level, more precisely, by its width
relative to the mean. But ex ante it is unknown how many observations have to be collected
to achieve this level – it must be determined runtime.
Before the simulation can be run in parallel under Akaroa, you have to start up the system:
where command is the name of the simulation you want to start. Parameters for Akaroa
are read from the file named Akaroa in the working directory. Collected data from the pro-
cesses are sent to the akmaster process, and when the required precision has been reached,
akmaster tells the simulation processes to terminate. The results are written to the stan-
dard output.
193
OMNeT++ Manual – Running The Simulation
The above description is of course not detailed enough help you set up and successfully use
Akaroa – for that you need to read the Akaroa manual. The purpose was rather to give you
the “flavour” of using it.
First of all, you have to compile OMNeT++ with Akaroa support enabled.
The OMNeT++ simulation must be configured in omnetpp.ini so that it passes the observa-
tions to Akaroa. The simulation model itself does not need to be changed (except for RNGs,
see later) – it continues to write the observations into output vectors (cOutVector objects,
see chapter 7). You can place some of the output vectors under Akaroa control.
You need to add the following to omnetpp.ini:
[General]
outputvectormanager-class="AkOutputVectorManager"
The above line replaces the normal output vector handler by its Akaroa-enabled version,
using the Envir plugin interface.
Also, you have to specify which output vectors you want to be under Akaroa control. By
default, all output vectors are under Akaroa control; the
<modulename>.<vectorname>.akaroa=false
setting can be used to make Akaroa ignore specific vectors. If you only want a few vectors be
placed under Akaroa, you can use the following “trick”:
<modulename>.<vectorname1>.akaroa=true
<modulename>.<vectorname2>.akaroa=true
...
*.*.akaroa=false
It is usually practical to have the same physical disk mounted (e.g. via NFS or Samba) on
all computers in the cluster. However, because all OMNeT++ simulation processes run with
the same settings, they would overwrite each other’s output files. Your can prevent this from
happening using the fname-append-host ini file entry:
[General]
fname-append-host=yes
When turned on, it appends the host name to the names of the output files (output vector,
output scalar, snapshot files).
Another important point is that you cannot use the random number generators provided by
OMNeT++, but rather you have to obtain random numbers from Akaroa.
Unfortunately, OMNeT++’s RNGs do not (yet) know anything about Akaroa. Remember,
all simulation processes are run with exactly the same configuration – including the same
194
OMNeT++ Manual – Running The Simulation
RNG seeds. That is, if you’d use OMNeT++’s own RNGs, all simulation processes would use
identical random number streams, thus executing exactly the same sequence of events! This
is clearly not what you want or what Akaroa expects.
Akaroa provides the following random number functions. The underlying RNG is a Com-
bined Multiple Recursive pseudorandom number generator (CMRG) with a period of approx-
imately 2191 random numbers, and provides a unique stream of random numbers for every
simulation engine.
Future versions of OMNeT++ will wrap the random number functions of Akaroa, thus re-
quiring no modifications to simulation programs.
#include <akaroa/distribution.H>
OMNeT++ detected that the module has used more stack space than it has allocated. You
should increase the stack for that module type. You can call the stackUsage() from fin-
ish() to find out actually how much stack the module used.
The resolution depends on whether you are using OMNeT++ on Unix or on Windows.
Unix. If you get the above message, you have to increase the total stack size (the sum of all
coroutine stacks). You can do so in omnetpp.ini:
[General]
total-stack-kb = 2048 # 2MB
There is no penalty if you set total-stack-kb too high. I recommend to set it to a few K
less than the maximum process stack size allowed by the operating system (ulimit -s; see
next section).
Windows. You need to set a low (!) “reserved stack size” in the linker options, for example
64K (/stack:65536 linker flag) will do. The “reserved stack size” is an attribute in the Win-
dows exe files’ internal header. It can be set from the linker, or with the editbin Microsoft
195
OMNeT++ Manual – Running The Simulation
utility. You can use the opp_stacktool program (which relies on another Microsoft utility
called dumpbin) to display reserved stack size for executables.
You need a low reserved stack size because the Win32 Fiber API which is the mechanism
underlying activity() uses this number as coroutine stack size, and with 1MB being the
default, it is easy to run out of the 2GB possible address space (2GB/1MB=2048).
A more detailed explanation follows. Each fiber has its own stack, by default 1MB (this is the
“reserved” stack space – i.e. reserved in the address space, but not the full 1MB is actually
“committed”, i.e. has physical memory assigned to it). This means that a 2GB address space
will run out after 2048 fibers, which is way too few. (In practice, you won’t even be able
to create this many fibers, because physical memory is also a limiting factor). Therefore,
the 1MB reserved stack size (RSS) must be set to a smaller value: the coroutine stack size
requested for the module, plus extra-stack-for-envir, the sum of the two typically around
32K. Unfortunately, the CreateFiber() Win32 API doesn’t allow the RSS to be specified. The
more advanced CreateFiberEx() API which accepts RSS as parameter is unfortunately only
available from Windows XP.
The alternative is the stacksize parameter stored in the EXE header, which can be set via
the STACKSIZE .def file parameter, via the /stack linker option, or on an existing executable
using the editbin /stack utility. This parameter specifies a common RSS for the main program
stack, fiber and thread stacks. 64K should be enough. This is the way simulation executable
should be created: linked with the /stack:65536 option, or the /stack:65536 parameter applied
using editbin later. For example, after applying the editbin /stacksize:65536 command to
dyna.exe, I was able to successfully run the Dyna sample with 8000 Client modules on my
Win2K PC with 256M RAM (that means about 12000 modules at runtime, including about
4000 dynamically created modules.)
“Segmentation fault”
On Unix, if you set the total stack size higher, you may get a segmentation fault during
network setup (or during execution if you use dynamically created modules) for exceeding
the operating system limit for maximum stack size. For example, in Linux 2.4.x, the default
stack limit is 8192K (that is, 8MB). The ulimit shell command can be used to modify the
resource limits, and you can raise the allowed maximum stack size up to 64M.
$ ulimit -s 65500
$ ulimit -s
65500
Further increase is only possible if you’re root. Resource limits are inherited by child pro-
cesses. The following sequence can be used under Linux to get a shell with 256M stack limit:
$ su root
Password:
# ulimit -s 262144
# su andras
$ ulimit -s
262144
If you do not want to go through the above process at each login, you can change the limit in
the PAM configuration files. In Redhat Linux (maybe other systems too), add the following
line to /etc/pam.d/login:
196
OMNeT++ Manual – Running The Simulation
A more drastic solution is to recompile the kernel with a larger stack limit.
Edit /usr/src/linux/include/linux/sched.h and increase _STK_LIM from
(8*1024*1024) to (64*1024*1024).
Finally, it you’re tight with memory, you can switch to Cmdenv. Tkenv increases the stack
size of each module by about 32K so that user interface code that is called from a simple
module’s context can be safely executed. Cmdenv does not need that much extra stack.
Eventually...
Once you get to the point where you have to adjust the total stack size to get your program
running, you should probably consider transforming (some of) your activity() simple mod-
ules to handleMessage(). activity() does not scale well for large simulations.
• memory leaks, that is, forgetting to delete objects or memory blocks no longer used;
• crashes, usually due to referring to an already deleted object or memory block, or trying
to delete one for a second time;
• heap corruption (enventually leading to crash) due to overrunning allocated blocks, i.e.
writing past the end of an allocated array.
By far the most common ways leaking memory in simulation programs is by not deleting
messages (cMessage objects or subclasses). Both Tkenv and Cmdenv are able to display
the number of messages currently in the simulation, see e.g. section 9.3.3. If you find that
the number of messages is steadily increasing, you need to find where the message objects
are. You can do so by selecting Inspect|From list of all objects... from the Tkenv menu, and
reviewing the list in the dialog that pops up. (If the model is large, it may take a while for
the dialog to appear.)
If the number of messages is stable, it’s still possible you’re leaking other cObject-based
objects. You can also find them using Tkenv’s Inspect|From list of all objects... function.
If you’re leaking non-cObject-based objects or just memory blocks (structs, int/double/struct
arrays, etc, allocated by new), you cannot find them via Tkenv. You’ll probably need a spe-
cialized memory debugging tool like the ones described below.
If you suspect that you may have memory allocation problems (crashes associated with
double-deletion or accessing already deleted block, or memory leaks), you can use special-
ized tools to track them down. 1
1 Alternatively, you can go through the full code, review it looking for bugs. In my experience, the latter one has
proved to be far more efficient than using any kind of memory debugger.
197
OMNeT++ Manual – Running The Simulation
The number one of these tools is Purify from Rational Software. This is a commercial tool.
Not particularly cheap, but it has very good reputation and proved its usefulness many times.
There are several open source tools you can try. The best seems to be Valgrind used by
the KDE people. Other good ones are NJAMD, MemProf, MPatrol and dmalloc, while Elec-
tricFence seems to be included in most Linux distributions. Most of the above tools support
tracking down memory leaks as well as detecting double deletion, writing past the end of an
allocated block, etc.
However, if you don’t have such tools, you can use the basic heap debugging code in Cm-
denv. It is disabled by default; to turn it on, you have to uncomment the #defines in
src/envir/cmdenv/heap.cc:
If COUNTBLOCKS is turned on, you should see the [heap.cc-DEBUG:ALL BLOCKS FREED
OK] message at the end of the simulation. If you do not see it, it means that some blocks
have not been freed up properly, that is, your simulation program is likely to have memory
leaks.
198
OMNeT++ Manual – Analyzing Simulation Results
Chapter 10
199
OMNeT++ Manual – Analyzing Simulation Results
output vector is there, Plove can plot it for you. Moreover, it can also work with gzipped
vector files without extracting them – just make sure you have zcat.
Plove never modifies the output vector files themselves.
On startup, Plove automatically reads the .ploverc file in your home directory. The file
contains general gnuplot settings, the filter configuration etc. (that is, the stuff from the
Options menu).
10.2.2 Usage
First, you load an output vector file (.vec) into the left pane. You can also load gzipped vector
files (.vec.gz) without having to decompress them. You can copy vectors from the left pane
to the right pane by clicking the right arrow icon in the middle. The large PLOT button
will plot the selected vectors in the right pane. Selection works as in Windows: dragging and
shift+left click selects a range, and ctrl+left click selects/deselects individual items. To adjust
drawing style, change vector title or add filter, push the Options... button. This works for
several selected vectors too. Plove accepts nc/mc-like keystrokes: F3, F4, F5, F6, F8, grey ’+’
and grey ’*’.
The left pane works as a general storage for vectors you’re working with. You can load
several vector files, delete vectors you don’t want to deal with, rename them etc. All this will
not affect the vector files on disk. In the right pane, you can duplicate vectors if you want
to filter the vector and also keep the original. If you set the right options for a vector but
temporarily do not want it to hang around in the right pane, you can put it back into the left
pane for storage.
Before the filter pipeline is launched, the following substitutions are performed on the awk
scripts:
• t gets substituted to $2, which is the simulation time (the second column in the output
vector file)
200
OMNeT++ Manual – Analyzing Simulation Results
The parameters of the form $(paramname) are also replaced with their actual value.
For example, if you want to add 1 to all values, you can use the awk expression filter x+1. It
will turn into the following awk script:
When you want to shift the vector by a used-defined DT time, you can create the following
awk program filter:
{t += $(DT); print}
Do not forget the print statement, or your filter will not output anything and the gnuplot
graph will be empty.
Filters are automatically saved into and loaded from the /.ploverc file.
mysim.vec:
vector 1 "subnet[4].term[12]" "response time" 1
1 12.895 2355.66666666
1 14.126 4577.66664666
vector 2 "subnet[4].srvr" "queue length" 1
2 16.960 2.00000000000.63663666
1 23.086 2355.66666666
2 24.026 8.00000000000.44766536
There are label lines (beginning with vector) and data lines.
A vector line introduces a new vector. Its columns are: vector ID, module of creation, name
of cOutVector object, multiplicity of data (single numbers or pairs will be written).
Lines beginning with numbers are data lines. The columns: vector ID, current simulation
time, and one or two double values.
201
OMNeT++ Manual – Analyzing Simulation Results
Or, you can get the list of all vectors in the file by typing:
Pick the vector ID, which is 6 in this case, and grep the file for the vector’s data lines:
Now, vector6.vec contains the appropriate vector. The only potential problem is that the
vector ID is there at the beginning of each line and this may be hard to explain to some
programs that you use for post-processing and/or visualization. This problem is eliminated
by the OMNeT++ splitvec utility (written in awk), to be discussed in the next section.
% splitvec mysim.vec
mysim1.vec:
# vector 1 "subnet[4].term[12]" "response time" 1
12.895 2355.66666666
14.126 4577.66664666
23.086 2355.66666666
mysim2.vec:
# vector 2 "subnet[4].srvr" "queue length" 1
16.960 2.00000000000.63663666
24.026 8.00000000000.44766536
202
OMNeT++ Manual – Analyzing Simulation Results
There are several commands to adjust ranges, plotting style, labels, scaling etc. Gnuplot can
also plot 3D graphs. Gnuplot is also available for Windows and other platforms. Gnuplot also
has a simple graphical interactive user interface called PlotMTV. However, we recommend
that you use OMNeT++’s Plove tool, described in an earlier section.
Xmgr is an X/Motif based program, with a menu-driven graphical interface. You load the
appropriate file by selecting in a dialog box. The icon bar and menu commands can be used
to customize the graph. Some say that Xmgr can produce nicer output that Gnuplot and it is
easier to use. Xmgr cannot do 3D and only runs on Unixes with X and Motif installed. Xmgr
also has a batch interface so you can use it from scripts too.
203
OMNeT++ Manual – Analyzing Simulation Results
204
OMNeT++ Manual – Parallel Execution
Chapter 11
Parallel Execution
OMNeT++ supports parallel execution of large simulations. The following paragraphs pro-
vide a brief picture of the problems and methods of parallel discrete event simulation (PDES).
Interested readers are strongly encouraged to look into the literature.
For parallel execution, the model is to be partitioned to several LPs that will be simulated
independently on different hosts or processors. Each LP will have its own local Future Event
Set, thus they will maintain local simulation times. The main issue with parallel simula-
tions is keeping LPs synchronized in order to avoid violating causality of events. Without
synchronization, a message sent by one LP could arrive in another LP when the simulation
time in the receiving LP has already passed the timestamp (arrival time) of the message.
This would break causality of events in the receiving LP.
There are two broad categories of parallel simulation algorithms that differ in the way they
solve the causality problem outlined above:
205
OMNeT++ Manual – Parallel Execution
11.1.2 In work
Parallel simulation support in OMNeT++ has recently been reimplemented. The new code is
currently being refined and tested, and it will be available in the following releases.
206
OMNeT++ Manual – Customization and Embedding
Chapter 12
12.1 Architecture
OMNeT++ has a modular architecture. The following diagram shows the high-level architec-
ture of OMNeT++ simulations:
• Sim is the simulation kernel and class library. Sim exists as a library you link your
simulation program with. 1
• Envir is another library which contains all code that is common for all user interfaces.
main() is also in Envir. Envir provides services like ini file handling for specific user
interface implementations. Envir presents itself towards Sim and the executing model
via the ev facade object, hiding all other user interface internals. Some aspects of Envir
can be customized via plugin interfaces. Embedding OMNeT++ into applications can be
achieved implementing a new user interface in addition to Cmdenv and Tkev, or by
replacing Envir with another implementation of ev (see sections 12.5.3 and 12.2.)
1 Use of dynamic (shared) libraries is also possible, but for simplicity we’ll use the word linking here.
207
OMNeT++ Manual – Customization and Embedding
• Cmdenv and Tkenv are specific user interface implementations. A concrete simula-
tion is linked with either Cmdenv or Tkenv.
• The Model Component Library is the simple module definitions and their C++ im-
plementations, compound module types, channels, networks, message types and in gen-
eral everything that belong to models and has been linked into the simulation program.
A simulation program is able to run any model that has all necessary components
linked in.
• The Executing Model is the model that has been set up for simulation. It contains
objects (modules, channels, etc.) that are all instances of components in the model
component library.
The arrows in the figure show how functional blocks interact with each other:
• Executing Model vs Sim. The simulation kernel manages the future events and
invokes modules in the executing model as events occur. The modules of the executing
model are stored in the main object of Sim, simulation (of class cSimulation). In
turn, the executing model calls functions in the simulation kernel and uses classes in
the Sim library.
• Sim vs Model Component Library. The simulation kernel instantiates simple mod-
ules and other components when the simulation model is set up at the beginning of
the simulation run. It also refers to the component library when dynamic module cre-
ation is used. The machinery for registering and looking up components in the model
component library is implemented as part of Sim.
• Executing Model vs Envir. The ev object, logically part of Envir, is the facade of the
user interface towards the executing model. The model uses ev for writing debug logs
(ev«, ev.printf()).
• Sim vs Envir. Envir is in full command of what happens in the simulation program.
Envir contains the main() function where execution begins. Envir determines which
models should be set up for simulation, and instructs Sim to do so. Envir contains the
main simulation loop (determine-next-event, execute-event sequence) and invokes the
simulation kernel for the necessary functionality (event scheduling and event execution
are implemented in Sim). Envir catches and handles errors and exceptions that occur
in the simulation kernel or in the library classes during execution. Envir presents a
single facade object (ev) that represents the environment (user interface) toward Sim –
no Envir internals are visible to Sim or the executing model. During simulation model
setup, Envir supplies parameter values for Sim when Sim asks for them. Sim writes
output vectors via Envir, so one can redefine the output vector storing mechanism by
changing Envir. Sim and its classes use Envir to print debug information.
• Envir vs Tkenv and Cmdenv. Envir defines TOmnetApp as a base class for user in-
terfaces, and Tkenv and Cmdenv both subclass from TOmnetApp. The main() function
provided as part of Envir determines the appropriate user interface class (subclassed
from TOmnetApp), creates an instance and runs it – whatever happens next (opening a
GUI window or running as a command-line program) is decided in the run() method
of the appropriate TOmnetApp subclass. Sim’s or the model’s calls on the ev object are
simply forwarded to the TOmnetApp instance. Envir presents a framework and base
functionality to Tkenv and Cmdenv via the methods of TOmnetApp and some other
classes.)
208
OMNeT++ Manual – Customization and Embedding
• Portable coroutine package creates all coroutine stacks inside the main stack. It is
based on Kofoed’s solution[Kof95]. It allocates stack by deep-deep recursions and then
plays with setjmp() and longjmp() to switch from one another.
• On Windows, the Fiber functions (CreateFiber(), SwitchToFiber(), etc) are used,
which are part of the standard Win32 API.
209
OMNeT++ Manual – Customization and Embedding
The coroutines are represented by the cCoroutine class. cSimpleModule has cCoroutine
as one a base class.
% ./fddi -h
Available modules:
FDDI_MAC
FDDI_MAC4Ring
...
Available channels:
...
End run of OMNeT++
Information on components are kept on registration lists. There are macros for registering
components (that is, for adding them to the registeration lists): Define_Module(), De-
fine_Module_Like(), Define_Network(), Define_Function(), Register_Class(),
and a few others. For components defined in NED files, the macro calls are generated by the
NED compiler; in other cases you have to write them in your C++ source.
The machinery for managing the registrations lists are part of the Sim library. Registration
lists are implemented as global objects.
The registration lists are:
210
OMNeT++ Manual – Customization and Embedding
cEnvir ev;
211
OMNeT++ Manual – Customization and Embedding
cEnvir basically a facade, its member functions contain little code. cEnvir maintains a
pointer to a dynamically allocated simulation application object (derived from TOmnetApp,
see later) which does all actual work.
cEnvir member functions deal with four basic tasks:
• I/O for module activities; actual implementation is different for each user interface (e.g.
stdin/stdout for Cmdenv, windowing in Tkenv)
• provides functions called by simulation kernel objects to get information (for example,
get module parameter settings from the configuration file)
• provides functions called by simulation kernel objects to notify the user interface of
some events. This is especially important for windowing user interfaces (Tkenv), be-
cause the events are like this: an object was deleted so its inspector window should be
closed; a message was sent so it can be displayed; a breakpoint was hit.
The above interfaces are documented in the API Reference, generated by Doxygen.
The corresponding ini file entries that allow you to select your plugin classes are outputvectormanager-
class, outputscalarmanager-class and snapshotmanager-class, documented in sec-
tion 9.2.6.
• Some of them implement the cEnvir functions (described in the previous section)
• Others implement the common part of all user interfaces (for example: reading options
from the configuration files; making the options effective within the simulation kernel)
212
OMNeT++ Manual – Customization and Embedding
• The run() function is pure virtual (it is different for each user interface).
213
OMNeT++ Manual – Customization and Embedding
214
OMNeT++ Manual – NED Language Grammar
Appendix A
The NED language, the network topology description language of OMNeT++ will be given
using the extended BNF notation.
Space, horizontal tab and new line characters counts as delimiters, so one or more of them is
required between two elements of the description which would otherwise be unseparable. ’//’
(two slashes) may be used to write comments that last to the end of the line. The language
only distinguishes between lower and upper case letters in names, but not in keywords.
In this description, the {xxx...} notation stands for one or more xxx’s separated with spaces,
tabs or new line characters, and {xxx„,} stands for one or more xxx’s, separated with a comma
and (optionally) spaces, tabs or new line characters.
For ease of reading, in some cases we use textual definitions. The networkdescription symbol
is the sentence symbol of the grammar.
notation meaning
[a] 0 or 1 time a
{a} a
{a,,,} 1 or more times a, separated by commas
{a...} 1 or more times a, separated by spaces
a|b a or b
‘a’ the character a
bold keyword
italic identifier
networkdescription ::=
{ definition... }
definition ::=
include
| channeldefinition
| simpledefinition
| moduledefinition
| networkdefinition
include ::=
INCLUDE { fileName ,,, } ;
channeldefinition ::=
215
OMNeT++ Manual – NED Language Grammar
CHANNEL channeltype
[ DELAY numericvalue ]
[ ERROR numericvalue ]
[ DATARATE numericvalue ] $^******$
ENDCHANNEL
simpledefinition ::=
SIMPLE simplemoduletype
[ machineblock ]
[ paramblock ]
[ gateblock ]
ENDSIMPLE [ simplemoduletype ]
moduledefinition ::=
MODULE compoundmoduletype
[ machineblock$^*$ ]
[ paramblock ]
[ gateblock ]
[ submodblock ]
[ connblock ]
ENDMODULE [ compoundmoduletype ]
moduletype ::=
simplemoduletype | compoundmoduletype
machineblock ::=
MACHINES: { machine ,,, } ;
paramblock ::=
PARAMETERS: { parameter ,,, } ;
parameter ::=
parametername
| parametername : CONST [ NUMERIC ]
| parametername : STRING
| parametername : BOOL
| parametername : CHAR
| parametername : ANYTYPE
gateblock ::=
GATES:
[ IN: { gate ,,, } ; ]
[ OUT: { gate ,,, } ; ]
gate ::=
gatename [ ’[]’ ]
submodblock ::=
SUBMODULES: { submodule... }
submodule ::=
{ submodulename : moduletype [ vector ]
[ on_block$^*$... ]
[ substparamblock... ]
[ gatesizeblock... ] }
216
OMNeT++ Manual – NED Language Grammar
on_block$^*$ ::=
ON [ IF expression ]: { on_machine ,,, } ;
substparamblock ::=
PARAMETERS [ IF expression ]:
{ substparamname = substparamvalue,,, } ;
substparamvalue ::=
( [ ANCESTOR ] [ REF ] name )
| parexpression
gatesizeblock ::=
GATESIZES [ IF expression ]:
{ gatename vector ,,, } ;
connblock ::=
CONNECTIONS [ NOCHECK ]: { connection ,,, } ;
connection ::=
normalconnection | loopconnection
loopconnection ::=
FOR { index... } DO
{ normalconnection ,,, } ;
ENDFOR
index ::=
indexvariable ’=’ expression ‘‘...’’ expression
normalconnection ::=
{ gate { --> | <-- } gate [ IF expression ]}
| {gate --> channel --> gate [ IF expression ]}
| {gate <-- channel <-- gate [ IF expression ]}
channel ::=
channeltype
| [ DELAY expression ] [ ERROR expression ] [ DATARATE expression ]
$^******$
gate ::=
[ modulename [vector]. ] gatename [vector]
networkdefinition ::=
NETWORK networkname : moduletype
[ on_block ]
[ substparamblock ]
ENDNETWORK
217
OMNeT++ Manual – NED Language Grammar
parexpression ::=
expression | otherconstvalue
expression ::=
expression + expression
| expression - expression
| expression * expression
| expression / expression
| expression % expression
| expression ^ expression
| expression == expression
| expression != expression
| expression < expression
| expression <= expression
| expression > expression
| expression >= expression
| expression ? expression : expression
| expression AND expression
| expression OR expression
| NOT expression
| ’(’ expression ’)’
| functionname ’(’ [ expression ,,, ] ’)’ $^***$
| - expression
| numconstvalue
| inputvalue
| [ ANCESTOR ] [ REF ] parametername
| SIZEOF$^****$ ’(’ gatename ’)’
| INDEX$^*****$
numconstvalue ::=
integerconstant | realconstant | timeconstant
otherconstvalue ::=
’characterconstant’
| "stringconstant"
| TRUE
| FALSE
inputvalue ::=
INPUT ’(’ default , "prompt-string" ’)’
default ::=
expression | otherconstvalue
∗
used with distributed execution
∗∗
used with the statistical synchronization method
∗∗∗
max. three arguments. The function name must be declared in the C++ sources with the
Define_Function macro.
∗∗∗∗
Size of a vector gate.
∗∗∗∗∗
Index in submodule vector.
∗∗∗∗∗∗
Can appear in any order.
218
OMNeT++ Manual – REFERENCES
References
219
OMNeT++ Manual – REFERENCES
220
OMNeT++ Manual – INDEX
Index
221
OMNeT++ Manual – INDEX
cInifile, 213 cQueue, 22, 84, 117, 120, 125–127, 153, 154,
cKSplit, 117, 124, 137–139, 143 158, 159, 162–165
class, 108, 110 cQueue::Iterator, 126
class data members, 63 create(), 93, 94
className(), 108, 116, 118, 119 CreateFiber(), 209
cLinkedList, 117, 127 createOne(), 155, 211
cLinkType, 211 createScheduleInit(), 93
cLongHistogram, 117, 124, 137 creationTime(), 100
Cmdenv, 146, 179 cSimpleChannel, 95
cMessage, 17, 55, 97–102, 104, 105, 107, 108, cSimpleModule, 17, 54, 59, 60, 62, 65, 69, 83,
113, 115, 117, 118, 125, 158, 160, 119, 160, 210
162, 197 cSimulation, 91, 208, 209
cMessageHeap, 56 cSnapshotManager, 179, 212
cMessages, 160 cStatistic, 133, 137, 144
cModule, 54, 83, 87–89, 91, 117 cStdDev, 117, 137, 138, 140
cModuleType, 61, 92, 93, 211 cSubModIterator, 91
cNetworkType, 210 cTDExpandingWindows, 144
cObject, 75, 97, 101, 108, 110, 113, 118, 119, cTopology, 117, 134–136
125–127, 133, 147, 153–159, 161–165, cTransientDetection, 144
197, 211 customization, 207
cObject *dup() const, 154 cVarHistogram, 117, 124, 137, 141
collect(), 138 cWatch, 117, 146, 147
command line switches, 179, 184 cWeightedStdDev, 137
command line user interface, 179 cWeightedStddev, 117
configPointer(), 130, 133
configure script, 170 data rate, 57
connect(), 94 data rate change, 89
connection, 8, 32, 33 dblrand(), 123
conditional, 34, 43 debugging, 11, 146, 171
loop, 33 decapsulate(), 99
connectTo(), 94, 95 default-run, 184
const, 26 defaultOwner(), 164
const char*, 107 Define_Function(), 39, 210, 211
context pointer, 100 Define_Function2(), 40
contextPointer(), 100 Define_Link(), 211
copy constructor, 120 Define_Module(), 59–61, 92, 210, 211
copyNotSupported(), 157 Define_Module_Like(), 59, 61, 210, 211
coroutine, 17, 55, 62, 66, 67, 73, 94, 209 Define_Network(), 210
stack size, 68 delayed sending, 83
COUNTBLOCKS, 198 delete, 163, 165
cout, 146 deleteModule(), 94
cOutputScalarManager, 179, 212 density estimation, 199
cOutputVectorManager, 179, 212 DES, 53
cOutVector, 117, 145, 187, 194, 199, 201, 212 destinationGate(), 90
cPacket, 100, 117 detect(), 144
cPar, 87, 95, 99, 101, 115, 117, 125, 128–133, Dijkstra algorithm, 136
140, 158–160, 165, 211 disable(), 137
expressions, 130 discard(), 163
cPar types and member functions, 132 discrete event simulation, 53
cPar memory management, 130 display strings, 40, 152, 187
cPars, 133 tags, 41
cPSquare, 117, 124, 137–139 wildcards, 188
CPU time, 54 DISPLAYALL, 198
cpu-time-limit, 178, 179 DISPSTRAYS, 198
222
OMNeT++ Manual – INDEX
223
OMNeT++ Manual – INDEX
224
OMNeT++ Manual – INDEX
225
OMNeT++ Manual – INDEX
226
OMNeT++ Manual – INDEX
227
OMNeT++ Manual – INDEX
variance(), 138
virtual, 105
virtual time, 54
void info(char *), 155
void writeContents(ostream&), 155
228