-
Notifications
You must be signed in to change notification settings - Fork 8
Extending basics
The development of applications is based on some basic principles. It is well defined where in the directory hierarchy an application is located, and that each application can be enabled or disabled via CMake. Moreover, it consists of either processors or simulation tasks or both. Finally, also external libraries can be included in the CMake configuration. All of these principles are described next.
When adding a new application,
the first issue is where to create the new folder. There are two
different locations possible: shawn/src/apps
, and a so called
legacyapps
-folder. The former is directly available after
checking out Shawn from svn. It contains well thought-out, reasonable,
and thoughtfully implemented applications that follow the general
coding guidelines. New applications should only be added with
agreement of the project leaders. On the other hand, there is the
legacyapps
-folder that must be created manually. This one can
be used for any kind of self-implemented application, because they are
not under version control of Shawn. For example, own applications can
be held under own version control. After creating this folder, it must
be set in the CMake configuration as described later.
After the legacyapps
-folder has been created, applications
can be added. In general they consist of processors and tasks and
other code. The files which form an application are organized as a
separate software component which is also called a module. The
advantage of a module is that it could be in- or excluded from the
build process by CMake. Each directory located directly under
legacyapps
is assumed to be a module.
After downloading Shawn,
the src
-directory contains three folders: apps
,
frontend
, and sys
. Create a new directory that is
called legacyapps
, copy apps_init.h
and
apps_init.cpp
to the newly created folder, and rename them
to legacyapps_init.*
. Then, replace every occurrence of
apps and APPS with legacyapps and LEGACYAPPS, respectively,
in these files.
Change to shawn/buildfiles
and call ccmake ../src
.
Go to the line named LEGACYAPPS_PATH
, press
Enter
, and type the complete, absolute path to the newly
created legacyapps
-folder. Then press Enter
again,
then c
to configure, and at last q
to quit. Now, own
applications (modules) can be added.
To create a new module, create a
folder in the legacyapps
-directory. Here you can create
subfolders for your processors and tasks or any further code. Then
copy an existing module.cmake
file (e.g., from any
application in shawn/src/apps/
) in your folder for your
application and change the moduleName
to the name of your
application/module. Here is a module.cmake
you have to
create:
#=============
# Shawn module configuration for cmake build system
#=============
# Name of this module
set ( moduleName SAME_AS_DIRECTORY_NAME )
# Default status (ON/OFF)
set ( moduleStatus OFF )
# List of libraries needed by this module, separated
# by white space
set ( moduleLibs )
The moduleName here is the same as the directory name, but written in
upper case characters instead of lower case . You will see the effect
while running CMake after pressing c
. There is a new menu
item like MODULE_LEGACYAPPS_your_module_name
(make sure
that option CONFIGURE_LEGACYAPPS
is set to ON
)
which could be switched ON
or OFF
. But how does the
compiler know if the code is switched on or off? This is done by the
preprocessor directive #ifdef
. All your code must be
surrounded by #ifdef ENABLE_your_module_name
and
#endif
. Make sure that the directive #include "../buildfiles/_legacyapps_enable_cmake.h"
is predecessing
#ifdef ENABLE_your_module_name
(in the
header-files). Thus, for example, a header starts with
#include "legacyapps/your_module_name/your_module_name_init.h"
#ifdef ENABLE_YOUR_MODULE
Tasks and processors must be initialized before you can use them in
Shawn. This is done by the initialization code. It is organized in the
two files your_module_name_init.h
and
your_module_name_init.cpp
. There is only one important
method named init_your_module_name
. See
shawn/src/apps/examples/examples_init.h
and
examples_init.cpp
for an example.
The init_module_name
method is called by Shawn when modules
are initialized. In the definition file (.cpp file) you have to
register your processor-factories and add your tasks to the
task_keeper
. This is explained later in Section
XXX.
Processors are useful for
distributed implementation of distributed algorithms and can be
attached to nodes that in turn are located in the simulated world. It
is also possible to attach multiple processors to a node, and thus
execute multiple independent algorithms concurrently in a
simulation. These algorithms can either run completely independent
from each other without even being aware of other processors, or can
exchange data over type-safe tags
(cp. SectionXXX or
SectionXXX) to solve a global task
collectively.
Processors are able to send and receive messages as well as performing
a working step in each simulation round. If, for example, there exists
a class MyMessage
that is derived from
shawn::Message
, a processor is able to send such a message
via
send( new MyMessage );
The sent message can then be received by the method
process_message()
that provides a handle
to the
received message. Since a processor can receive different message
types, it must be checked if the received one is of the expected
type. This is done as follows
const MyMessage* msg =
dynamic_cast<const MyMessage*>( handle.get() );
If msg
is not null, it is of the expected type.
Moreover, the virtual method work()
of a
shawn::Processor
that can be implemented by a derived class,
is called automatically in the beginning of each simulation round.
A processor can adopt three different states:
- active,
- sleep, and
- inactive.
An active one is able to receive messages and do the working step in each round, whereas the sleeping one only performs the working step without receiving messages. An inactive processor is out of order and can not get reactivated by itself. Thus, if all processors get inactive during a simulation, the simulation is aborted.
Moreover the methods boot()
and special_boot()
can
be implemented. boot()
is called only once (before the first
simulation round). special_boot()
is also called once per
processor, and also before the general boot()
method, but
only if the processor is attached to a special node. Whether
a node is special is chosen arbitrarily, but for exactly one
node in the network. Hence, the method can be used, for example, for
algorithms that require exactly one gateway.
Tasks are useful for centralized algorithms or a small look over the network topology. They are invoked by naming them in the configuration file.
They can be seen as a main-method from where to instantiate certain objects or start certain global actions and algorithms needed in your simulation. They have access to the Simulation Controller and consequently to the whole simulation environment.
To create a new task, it must be derived from
shawn::SimulationTask
, and override methods
- name(),
- description(), and
- run()
wherename()
is used for identifying the task by an unique name, so that it can be invoked from a configuration file, for example.description()
is used for getting information about a task. Mostly it is a one-liner that roughly describes the purpose of the task. Finally, when a task is invoked, the methodrun()
is executed, and provides also access to the simulation controller.
There is an example in shawn/apps/examples/simulationtask that can be used as a template for writing an own task.
If one uses an external
library such as Boost, Cairo, CGAL, or the like, it must be integrated
into the compilation process. The path of the include files and the
path of the library files must be selected. It is possible to add
these options to the CMake configuration when calling ccmake ../src
from buildfiles. An example using the Cairo library looks as
follows.
OPTION(OPT_ENABLE_CAIRO "Enable Cairo library support" OFF)
if ( OPT_ENABLE_CAIRO )
set ( DEFAULT_CAIRO_ROOT )
set ( LIB_PATH_CAIRO CACHE PATH "Path to CAIRO library" )
set ( INCLUDE_PATH_CAIRO CACHE PATH "Path to CAIRO includes" )
link_directories( LIB_PATH_CAIR )
include_directories ( INCLUDE_PATH_CAIR )
endif ( OPT_ENABLE_CAIRO )
if ( WIN32 AND NOT CYGWIN )
if ( OPT_ENABLE_CAIRO )
if ( LIB_PATH_CAIRO )
set ( LIB_CAIRO cairo.dll )
endif ( LIB_PATH_CAIRO )
endif ( OPT_ENABLE_CAIRO )
else ( WIN32 AND NOT CYGWIN )
if ( OPT_ENABLE_CAIRO )
if ( LIB_PATH_CAIRO )
set ( LIB_CAIRO cairo pixman-1 png freetype \
fontconfig crypt z )
endif ( LIB_PATH_CAIRO )
endif ( OPT_ENABLE_CAIRO )
endif ( WIN32 AND NOT CYGWIN )
IF(NOT BUILD_LIB_WITH_MAIN)
target_link_libraries( shawn LIB_CAIR )
ENDIF(NOT BUILD_LIB_WITH_MAIN)
if( OPT_ENABLE_CAIRO )
if ( LIB_PATH_CAIRO )
add_definitions( -DHAVE_CAIRO )
endif ( LIB_PATH_CAIRO )
endif( OPT_ENABLE_CAIRO )
First, the keyword OPTION
adds a new option to the CMake
configuration, with default turned off. Then, include and library
locations are added to the configuration, but only if Cairo is
enabled. Then, the libraries given to the linker are added, for
Windows as well as other platforms like Linux. At last, the definition
for letting the application source code know that Cairo is enabled is
added.
Hence, if one sets Enable Cairo library support to
ON
in CMake configuration, and then presses c
to
configure, the two lines Path to CAIRO library and
Path to CAIRO includes appear as additional options that can
be set by the user. On windows systems, for example, the library path
is used for finding the file cairo.dll
.