Tutorial: Writing a Component

A component is the basic software module that is used for the 2010 Mini Grand Challenge software solution. It is a stand-alone executable with several properties. A component is usually a single executable/program/process that either applies an algorithm or interfaces with hardware. Each component has some basic input and/or output. The implementation is always a class that inherits from the MGC Base Component class, and overloads the "Update" function. This is the "Component Model Design" as discussed in MGC Project Abstract.

A "component" is a modular piece of code that reads input, applies an algorithm, and returns an output. It is actually a single class that inherits from MGC Base Component. The notion "to inherit from" is a basic concept from Object Oriented Languages, such as C++ but not C. A component is similar to a driver found in the Player/Stage library, but simplified for clarity and performance. Each component is compiled into a single program that is a stand-alone process using CMU IPC for passing information to, and from, other components. Each component, based on the constructor, manages internal resources to better increase performance on a target system.

= Software Dependencies =

A C++ compiler is needed, as well as the CMU IPC library must be built and linked with your project. The CMU IPC library also contains an executable program called "central" which is a core component of the communication system. Before any components can communicate with each other, this "central" program must be running.

Most of the needed header file incudes can be founded within the "Shared/Utilities.h" file, though the only officially supported build process is Linux. Please see MGC Developer Tools for details.

= Basic Design =

All components are a single class that inherit from "BaseComponent". Within this class, you must write your own code for the "Update" function. This function is called from within a loop based on a performance measuring timer. Special code is also used such that "Update" is optimally called based on the same timer system. Each component can write out data to any listening components using "SendData". If a component wants to read data, it must first register that component using "SetDependency" in the constructor, and then calling "GetData" when appropriate.

= Basic Functions =

The base class "BaseComponent" is the most basic of all components. It is not to be used as a component by itself, but to be "inherited" by your own component. This class contains pure-virtual function "Update", which is called by an internal management system to better manage performance (and communications). Other functions exist for support, configuration file parsing, and inter component communications. The following are basic descriptions of each function that is available to a class that inherits from "BaseComponent".

Initialization
Initialization is as simple as you want it to be. The only necessary arguments to the super-class' constructor is your component's name and the fraction of a second you expect the "Update" function to work within. Each component name is not case sensitive, so "MySample" and "mySAMPLE" are in-fact the same. Each component name must also be unique, so two components called "LaserInterface" cannot be run in parallel.

The update time is critical to each component's performance. The reason why "Update" is a pure virtual function is that it is the only function you should really have to write code for (apart from some slight overhead code). To do this, you must "overload" this function. This is done by redeclaration of this function within your own component class (same name and returns/parameters), but with your own code in the body. Your own "Update" function is then called through an infinite loop measuring performance and managing internal resources. More about this function is discussed below.

Dependencies are initialized in the constructor. Simply calling "SetDependency" with the component name you would like to receive data from automatically starts managing internal communication and buffers. This is to make sure you receive all related data in your "Update" loop, no matter where this other component is. Using the "GetData" function you can retrieve the latest input. You do not need to explicitly state which components will read from your own component; this is done through the same function but in other component's code.

Starting a component
Each component is compiled to a single executable file and is ran as a stand-alone process. When the Minigrand10 solution is ran, it is actually several processes that being, each representing a single component. To do this, write a standard "main" function. Within this function, create an instance of your component, then call the instance's "Run" function.

Update
The "Update" function, as mentioned, is a pure-virtual function such that you must overload it to use it. "Update" is managed such that your algorithm / interface code within here is called once every fraction of second that was given to the constructor. If "0.1" was passed to the constructor, that means your custom "Update" function is called 10 times a second, or simply operates at 10Hz. If your function performs well and is done within 0.1 seconds, the BaseComponent will release resources (mainly processor time) such that other processes can better use it. In the other case in which your component is underperformant and takes longer to meet the expected update time, your update is not interrupted and is given more resources to work with (again, mainly processor time).

It is important to note that this is the basic component of process management/scheduling done by the operating system, though this method of timing does help the OS better decide time allocations per process.

Get Data Size
Returns the number of bytes associated with the component and data type names we want to read from. If the type does not exist or there was an error, a negative value is returned.

Get Data
Getting data is as easy as calling the common C programming language's "memcpy" function. The first parameter with "GetData" is the associated component we are reading from. Then, it is the data type name (Such as "VehiclePosition" for a tuple of lat/lon positions of the vehicle, or "LaserPoints" for an array of laser range finder points). The second is the data buffer to be written into. This buffer must be pre allocated, since the "BaseComponent" will not manage memory for you! You can retrieve the needed buffer size by calling the "GetDataByteCount" function.

The internal implementation copies the given memory such that there are no issues with data loss or corruption. If you dynamically allocate a buffer and pass it to the given function, you should release it when appropriate since the "GetData" function will never release it for you. None of these functions are interrupted with new data during the copy process, thus is data-update friendly and safe.

Send Data
When sending data, or more correctly "posting" data, all you need is the data type's name, the data itself, and the data block size. The internal implementation copies the given memory such that there are no issues with data loss or corruption. If you dynamically allocate a buffer and pass it to the given function, you should release it when appropriate since the "SendData" function will never release it for you.

Data is only actually sent when another component set it as a dependency. The data is queued internally until IPC wants to post the data, thus transferring it between the processes.

= Samples =

The following is a sample in which two components are created. Each component is in it's own single source file and must be built with "g++" separately. For simplicity, we do not separate the code into source and header files. The first component "GeneratePI" attempts to generate a value of PI over time. The second component "ReadPI" attempts to read a value of PI from the component "GeneratePI" and prints it onto standard out. To run this demo, you must create two executable files from each different source file, then run the CMU IPC central server, and finally run both processes. Only the ReadPI component prints out data to the console.

File GeneratePI.cpp

File ReadPI.cpp