-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial
#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!
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.
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.
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
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