Skip to content
Daniel Heater edited this page Nov 2, 2015 · 17 revisions

#Motivation This tutorial will walk you through some of the basics of building an application using libSPRITE.

libSPRITE provides for scheduling and deterministic data routing for multi-tasking applications on POSIX compliant operating systems. It also allows you to configure and control applications using the Lua scripting language.

libSPRITE hides some of the complexity of building multi-threaded applications and attempts to reduce the errors and indeterminism associated with concurrent programming. Especially when running on multi-core machines.

#Prerequisites

  • A C++ compiler
  • CMake
  • Lua development tools
  • Download and install libSPRITE.

#Hello World!

Step 1

A good first step is to build a simple, straight forward application that prints "Hello World!"

The source code for this step is available here

Create a file called sprite_main.cpp. For this first step, we are not going to use the SCALE Lua interface. We will stick with straight C/C++.

Start by creating a directory called task. This is where we will put source code for SRTX tasks. The write the following two source files.

Hello.hpp

#ifndef task_Hello_hpp
#define task_Hello_hpp

#include "SRTX/Task.h"

namespace task {
    class Hello : public SRTX::Task {

      public:
        /**
         * Constructor.
         * @param name Task name.
         */
        Hello(const char *const name);

        /**
         * Initialization routine.
         * @return true on success or false on failure.
         */
        bool init();

        /**
         * This the the function that gets executed on a periodic basis
         * each time this task is scheduler to run.
         * @return Return true to continue execution or false to terminate
         * the task.
         */
        bool execute();

        /**
         * Terminate routine.
         */
        void terminate();
    };

} // namespace

#endif // task_Hello_hpp

and Hello.cpp

#include "task/Hello.hpp"
#include "base/XPRINTF.h"

namespace task {

    Hello::Hello(const char *const name)
        : SRTX::Task(name)
    {
    }

    bool Hello::init()
    {
        return true;
    }

    bool Hello::execute()
    {
        IPRINTF("Hello World\n");
        return true;
    }

    void Hello::terminate()
    {
    }

} // namespace

In the top level directory, create the file sprite_main.cpp

#include <SRTX/Scheduler.h>
#include <base/XPRINTF.h>
#include <signal.h>

#include "task/Hello.hpp"

static volatile bool done(false);

static void kill_me_now(int)
{
    done = true;
}

static units::Nanoseconds HZ_to_period(unsigned int hz)
{
    return units::Nanoseconds(1*units::SEC / hz);
}

int main(void)
{
    /* Set up the signal handler for control-C.
     */
    signal(SIGINT, kill_me_now);

    /* Declare the task properties.
     */
    SRTX::Task_properties tp;
    SRTX::priority_t priority = SRTX::MAX_PRIO;

    /* Create the scheduler
     */
    tp.prio = priority;
    tp.period = HZ_to_period(1);
    SRTX::Scheduler &s = SRTX::Scheduler::get_instance();
    s.set_properties(tp);

    /* Create the "Hello World!" task
     */
    --tp.prio;
    task::Hello hello("Hello");
    hello.set_properties(tp);

    s.start();
    hello.start();

    while(!done)
    {
        ;
    }

    hello.stop();
    s.stop();

    return 0;
}

Then add a CMakeLists.txt file at the top level to build the project.

cmake_minimum_required(VERSION 2.8)

# I like to have the warning level set high.
add_definitions("-Wall -Wextra -Wparentheses -Wuninitialized -Wcomment -Wformat -Weffc++")

# Add the source tree directory to the search path for include files.
# Add the path to the libSPRITE header files.
include_directories(${CMAKE_CURRENT_SOURCE_DIR} "/usr/local/include/SPRITE")

# Set libSPRITE in the libraries path.
link_directories(/usr/local/lib/SPRITE)

# Add the executable and it's source.
add_executable(sprite_main sprite_main.cpp
               task/Hello.cpp)

# Specify libraries to link.
target_link_libraries(sprite_main SPRITE_SRTX rt pthread)

To build the executable, create a directory called build and cd into that directory. Execute cmake .. to generate the Makefiles. Then enter make to create an executable called sprite_main.

You must have root privledges to run the executable. This is required because tasks are set to real-time priorities. If you are not root, you will get a warning to run using sudo.

So what happens here. Well, we've created an executable that has one task called Hello. There is also a scheduler task running in the system and the Hello task is registered with the scheduler. Both the scheduler and the Hello task are set to run at 1 Hz with the scheduler running at the highest priority in the system and the Hello task running a the highest priority minus 1 --tp.prio.

The scheduler and Hello tasks are both started and continue to run until control-C is entered, at which point the Hello task and the scheduler are stopped and the program exits.

Bonus tip

So what's up with the IPRINTF("Hello World!\n")? The definition comes from the base/XPRINTF.h file in libSPRITE/base. It is a macro that can be turned on and off. IPRINTF stands for "information print". The base/XPRINTF.h header also contains macros for DPRINTF (debug), WPRINTF (warning), and EPRINTF (error).

By adding the line set_property(SOURCE task/Hello.cpp APPEND_STRING PROPERTY COMPILE_FLAGS " -DNO_PRINT_INFO") to CMakeLists.txt, you can turn off the print statement. This can be quite useful in controlling the level of verbosity on a file by file or system wide basis. Just use -DPRINT_DEBUG, -DNO_PRINT_WARNING, and -DNO_PRINT_ERROR to change the default behavior of DPRINTF, WPRINTF, and EPRINTF macros respectively. Note that the default behavior of DPRINTF is to not print.

Step 2

Source code for step 2 is available here

Notice that the execute() function in the Hello task returns true. When a task returns true it signals the scheduler that the task should be scheduled again. By returning false from the execute() function we can cause the scheduler to remove the task from the schedule. This slightly modified execute() function runs 5 times then exits.

bool Hello::execute()
{
    static unsigned int i = 0;
    IPRINTF("Hello World\n");
    return (++i < 5); 
}

Using SCALE

Step 3.

Next we will convert this simple world application to be executed using the SCALE bindings to the Lua scripting language.

Source code for step 3 is available here

First, we create a class that binds the Hello World task to Lua using the SCALE language extension.

task/Hello_lua.hpp

#ifndef __task_Hello_lua_hpp__
#define __task_Hello_lua_hpp__

#include "SCALE/LuaWrapper.h"
#include "task/Hello.hpp"

namespace task {

    class Hello_lua {
      public:
        /**
         * The name registered with Lua to describe the class.
         */
        static const char class_name[];

        /**
         * The set of methods being exposed to Lua through the adapter
         * class.
         */
        static luaL_Reg methods[];

        /**
         * Allocate a new instance of the Hello task.
         * @param L Pointer to the Lua state.
         * @return A pointer to the Task.
         */
        static Hello *allocator(lua_State *L)
        {
            return new Hello(luaL_checkstring(L, 1));
        }

        /**
         * Register the contents of this class as an adapter between Lua
         * and C++ representations of SRTX::Task.
         * @param L Pointer to the Lua state.
         * @return Number of elements being passed back through the Lua
         * stack.
         */
        static int register_class(lua_State *L)
        {
            luaW_register<Hello>(L, "Hello", NULL, methods, allocator);
            luaW_extend<Hello, SRTX::Task>(L);
            return 0;
        }
    };

    const char Hello_lua::class_name[] = "Hello";

    luaL_Reg Hello_lua::methods[] = { { NULL, NULL } };

} // namespace

#endif // __task_Hello_lua_hpp__

The above piece of code looks nasty (and it kinda is). It's doing some magic looking stuff right now. We'll get to explaining some of the pieces later, for now, know that as you create more tasks, you can usually just copy this code and change the name when you add new tasks.

Now we rewrite the main() function to use the Lua script in place of hard-coded C++ tasks.

sprite_main.cpp

#include <SCALE/Scale_if.h>
#include <base/XPRINTF.h>

#include "task/Hello_lua.hpp"

int main(int argc, char* argv[])
{
    (void)argc; // Supress unused variable warning.

    SCALE::Scale_if& scale = SCALE::Scale_if::get_instance();

    /* Register my tasks with with the Lua executive.
     */
    task::Hello_lua::register_class(scale.state());

    /* Execute the main script that drives the simulation.
     */
    if(false == scale.run_script(argv[1]))
    {
        EPRINTF("Failed executing script: %s\n", argv[1]);
        return -1;
    }

    return 0;
}

So main just got a lot simpler. Now we create a Lua script that defines the tasks, thier parameters, and starts and stops them.

hello.lua

package.path = '/usr/local/lib/SPRITE/?.lua;' .. package.path
local s = require 'scheduler'

--------------------------------------------------------------------------------
-- Initialize the tasks.
--------------------------------------------------------------------------------

-- Create task properties and set an initial priority.
tp = Task_properties.new()
priority = tp:MAX_USER_TASK_PRIO()

-- Create the scheduler.
SCHEDULER_PERIOD = s.HZ_to_period(1)
scheduler = s.create(tp, SCHEDULER_PERIOD, priority)
priority = priority - 1

-- Create the hello world task.
hello = Hello.new("Hello")
s.set_task_properties(hello, tp, SCHEDULER_PERIOD, priority)
priority = priority - 1

--------------------------------------------------------------------------------
-- Start up the tasks.
--------------------------------------------------------------------------------

-- Start everything up.
print "Starting tasks..."
scheduler:start()
hello:start()

-- Use debug to pause the script and let the tasks run.
print "Use control-D to cleanly terminate execution."
debug:debug()

--------------------------------------------------------------------------------
-- Terminate the tasks.
--------------------------------------------------------------------------------

print "...Exiting"

hello:stop()
scheduler:stop()

Notice that the Lua script is now doing much of the work that had been in the main() function. An advantage here is that tasks, task rates, priorities, etc, can all be modified in the Lua script without the need for recompiling the application. We'll see some additional benefits later.

To finish up, we need to modify the CMakeLists.txt file.

At the time of writing, CMake does not handle varying versions of Lua, so we'll supply our own module for finding the correct paths for Lua. At the top level source directory, create the directory path cmake/Modules and download this FindLua.cmake file.

Finally, we need to modify CMakelists.txt.

cmake_minimum_required(VERSION 2.8)

# I like to have the warning level set high.
add_definitions("-Wall -Wextra -Wparentheses -Wuninitialized -Wcomment -Wformat -Weffc++")

# Set target specific rules.
#set_property(SOURCE task/Hello.cpp APPEND_STRING PROPERTY COMPILE_FLAGS " -DNO_PRINT_INFO")

# Find Lua and set paths to Lua
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
find_package(Lua REQUIRED)
include_directories(${LUA_INCLUDE_DIR})
set(LIBS ${LIBS} ${LUA_LIBRARIES})

# Add the source tree directory to the search path for include files.
# Add the path to the libSPRITE header files.
include_directories(${CMAKE_CURRENT_SOURCE_DIR} "/usr/local/include/SPRITE")

# Set libSPRITE in the libraries path.
link_directories(/usr/local/lib/SPRITE)

# Add the executable and it's source.
add_executable(sprite_main sprite_main.cpp
               task/Hello.cpp)

# Specify libraries to link.
target_link_libraries(sprite_main SPRITE_SCALE SPRITE_SRTX SPRITE_units pthread rt ${LIBS})

# Copy Lua scripts to the build directory.
file(COPY hello.lua DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

Now to run the executable, we must supply the Lua script to be used with the executable. sudo ./sprite_main hello.lua

Clone this wiki locally