Skip to content
This repository has been archived by the owner on Aug 25, 2020. It is now read-only.

Extending basics

Marvin Frick edited this page May 28, 2013 · 1 revision

Principles of Application Development

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.

Creating an Application

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.

Creating Legacyapps-Directory

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.

Creating a Module

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.

Writing a Processor

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.

Writing a Task

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()
    where name() 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 method run() 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.

Integrating an External Library

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.