# Programming Conventions¶

In the following different programming conventions are introduced. These apply mainly to C++ code. For details about other languages, see also the Interfaces section.

## Namespaces¶

The header files of RobWork are distributed across a number of directories each having its own namespace. The structure of namespaces reflects the directory containing the code. For example

// Include header files:
#include <rw/models/WorkCell.hpp>
#include <rw/kinematics/Frame.hpp>
using namespace rw::models; //Namespace for WorkCell included by #include<rw/models/WorkCell.hpp>
using namespace rw::kinematics; //Namespace for Frame included by #include <rw/kinematics/Frame.hpp>


All classes related to the RobWorkStudio package are placed in a namespace rws. All classes related to RobWorkHardware are in the namespace rwhw;

## Libraries¶

All classes of the rw directory are provided in a single library named rw.

The subdirectories of the rwlibs directory each correspond to a different library. The subdirectory rwlibs/xyz corresponds to the library named rw_xyz and contains the objects in the namespace rwlibs::xyz. For example, suppose your program contains the following include statement:

#include <rwlibs/pathplanners/rrt/RRTPlanner.hpp>


To build this program, you should link with rw_pathplanners.

## C++ shared pointer conventions¶

The RobWork libraries make extensive use of non-copyable objects (such as object referred to by interface) shared by pointer between different algorithms. Ownership of objects is managed by the shared pointer type rw::common::Ptr. If an object needs access to a non-copyable object, the constructor of the object will conventionally take a rw::common::Ptr type as parameter.

Classes that are commonly referred to by shared pointer define a shortcut for this pointer type. If the class is named T, the name of the pointer type will be T::Ptr, and the type of the pointer will be rw::common::Ptr<T>. Often there will also be a T::CPtr defined for pointers to constant objects that can not be modified. This will give the type rw::common::Ptr<const T>.

Here are some examples of these such pointer types:

• rw::math::QMetric::Ptr

• rw::models::WorkCell::Ptr

• rw::proximity::CollisionDetector::Ptr

• rw::pathplanning::QSampler::Ptr

• rw::pathplanning::QToQPlanner::Ptr

Here are some examples of constructor functions for such objects:

• rw::math::MetricFactory::makeEuclidean()

• rw::proximity::CollisionDetector::make()

• rw::pathplanning::QSampler::makeUniform()

• rwlibs::pathplanners::RRTPlanner::makeQToQPlanner()

The rw::common::Ptr type differs from standard shared pointer implementations by allowing the pointer to refer to a stack allocated object or another object for which an entity has already taken the full ownership. To make the use of such objects easy, a pointer to T can be implicitly converted to Ptr<T>, but the implicitly constructed rw::common::Ptr type does not take ownership of the object. If the rw::common::Ptr type should take ownership of the entity, you must explicitly call the rw::common::ownedPtr() function. This example illustrates the idiom:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include using namespace rw::common; class T { public: typedef rw::common::Ptr Ptr; typedef rw::common::Ptr CPtr; static Ptr make() { return ownedPtr(new T); } }; 

In everyday programming, the construction of rw::common::Ptr types is managed by the constructor functions for the various objects. Only if you write your own extensions for interfaces in RobWork will you need to explicitly call rw::common::ownedPtr().

## Serialization¶

There are two main structures for performing serialization in RobWork.

First, there is a XML DOM parser interface that lets you easily read and write XML data. This interface is used throughout RobWork for reading and writing well defined data exchange formats. The XML DOM parser should not be used as a class serialization structure but rather in properly defined loader and saver factories. The XML DOM parser is especially useful as an extension mechanism for current parsing formats, eg. the user can write a plugin that plugs into an existing parsing implementation such as the workcell format.

Secondly, there is a generic serialization framework which enables serialization of classes. This framework enables serialization to different archive types and can be used for serializing many different formats, including binary and user defined formats.

### XML DOM parsing¶

The backend to the XML DOM parser is pluginable and the full features such as validation is therefore depending on the backend. Both boost property tree and xercess backends are supported.

Here is a small example on creating a DOM parser which parses from an input stream (instream)

DOMParser::Ptr parser = DOMParser::make();
parser->setSchema(schemaFileName);
DOMElem::Ptr root = parser->getRootElement();


The root class can now be used for parsing the xml data. Lets assume we have a xml file that looks like:

<group someboolproperty="true" someintproperty="10">
<elem-string> </elem-string>
<elem-int> </elem-int>
<elem-double-list>0.1 0.2 0.1</elem-double-list>
</group>

DOMELem::Ptr gelem = root->getElement("group"); // throws an error if "group" is not there
bool boolprop = gelem->getAttributeValueAsBool("someboolproperty", true); // defaults to true
int intprop = element->getAttributeValueAsInt("someintproperty", 2); // defaults to 2
// iterate over all elements in group
BOOST_FOREACH(DOMElem::Ptr child, gelem->getChildren()) {
if(child->isName("elem-string")) {
std::string str-value = child->getValue();
} else if (child->isName("elem-int")) {
int int-val = child->getValueAsInt();
} else if (child->isName("elem-double-list")) {
std::vector<double> double-val-list = child->getValueAsDoubleList(); // default seperator is space ' '
}
}


### Generic serialization¶

The generic serialization is not a centralized controlled serialization as the XML serialization described above. There are two main concepts to the generic serialization: Archive, Serializable

An Archive is an instance that formats data that can be read or written to it from a Serializable object/class. As such interfaces for both InputArchive and OutputArchive exists. A serializable class is a class that either inherits from the rw::common::Serializable (intrusive) or using an extrusive way that overwrites the functions

void rw::common::serialization::write(const THE_CLASS&, OutputArchive& oar);


In any case save and load methods should be determined for the individual serializable classes. In these methods read/write operations on the archive should be performed such as to store or restore the state of the class. The use of an archive can be quite simple:

INIArchive ar(std::cout);
ar.write(1, "length");
ar.write(1.120, "width");
ar.write(5.120, "height");
produce:
length : 1
width : 1,120
height : 5.120


As can be seen an identifier can be associated to the value that is to be serialized. This id makes type checking possible and should allways be used/defined. The archive interface define serialization of primitives (int, float, double, string) and vectors of primitives. More complex types will need to implement their own serialization pieced together from the primitives.

Finally, the archive also defines a way to enter scopes. A scope is simply a grouping of value-identifier pairs.

void write(const KDTreeQ<VALUE_TYPE>& out, OutputArchive& oarchive) {
oarchive.write(out._dim, "dim");
oarchive.write((int)out._nodes->size(), "nrNodes");
oarchive.write((boost::uint64_t)out._root, "rootId");
RW_ASSERT(out._nrOfNodes==out._nodes->size());
for(int i=0;i<out._nodes->size();i++) {
const TreeNode &node = (*out._nodes)[i];
oarchive.write((boost::uint64_t)&node, "id");
oarchive.write( node._axis, "axis");
oarchive.write( node._deleted, "del");
oarchive.write( node._left, "left");
oarchive.write( node._right, "right");
oarchive.write( node._kdnode->key, "Q");
oarchive.write( node._kdnode->value, "value");
}
}


### Archive types¶

There is currently: - an rw::common::INIArchive which prints in INI format - an rw::common::BINArchive which prints in binary compressed format

When developing code that can run in parallel it is encouraged that the RobWork rw::common::ThreadPool concept is used. The ThreadPool provides a flexible way for the user to specify the number of threads that should be used in multi-threaded applications. By adding work to a ThreadPool instead of launching separate threads, the user has the ability to limit the resources used by the application. At the same time the application is able to efficiently utilize the given resources.

In the loadFile function the isStopping() method is checked. If the rw::common::ThreadPool is stopped before work has finished, the work added to the pool should check the isStopping() function at regular intervals to allow the application to shutdown gracefully. This is especially important in long running tasks.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include #include using namespace rw::common; using rw::loaders::ImageLoader; using rw::sensor::Image; static boost::mutex outMutex; static boost::mutex imageMutex; static std::vector images; void loadFile(ThreadPool* pool, const std::string &file) { { boost::mutex::scoped_lock lock(outMutex); std::cout << "Loading " << file << std::endl; } Image::Ptr image; try { image = ImageLoader::Factory::load(file); } catch (Exception&) { image = NULL; } if (pool->isStopping()) return; { boost::mutex::scoped_lock lock(imageMutex); images.push_back(image); } } int main(int argc, const char* argv[]) { ThreadPool::Ptr pool = ownedPtr(new ThreadPool(std::atoi(argv[1]))); for (int i = 2; i < argc; i++) { ThreadPool::WorkFunction work = boost::bind(&loadFile,_1,argv[i]); pool->addWork(work); } pool->waitForEmptyQueue(); return 0; } 

When writing multi-threaded applications one often need to branch out into many parallel tasks, then wait for the tasks to finish and finally combine the results. Then one might need to branch out yet again into new parallel tasks based on the result. Also each parallel task might be able to parallize its work into even more subtasks. This can in principle happen in a hierachy where each subtask can have its own parallel subtasks and so forth.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include #include #include using namespace rw::common; using rw::loaders::ImageLoader; using rw::sensor::Image; static boost::mutex outMutex; class LoadTask: public ThreadTask { public: LoadTask(ThreadTask::Ptr parent, const std::string &file): ThreadTask(parent), _file(file) {}; Image::Ptr getImage() const { return _image; } void run() { { boost::mutex::scoped_lock lock(outMutex); std::cout << "Loading " << _file << std::endl; } try { _image = ImageLoader::Factory::load(_file); } catch (Exception&) { _image = NULL; } } private: const std::string _file; Image::Ptr _image; }; class MainTask: public ThreadTask { public: MainTask(ThreadPool::Ptr pool, const std::vector &files): ThreadTask(pool), _files(files) {}; std::vector getImages() { boost::mutex::scoped_lock lock(_imageMutex); return _images; } void run() { for(const std::string &file : _files) { LoadTask::Ptr task = ownedPtr(new LoadTask(this,file)); addSubTask(task); } } void subTaskDone(ThreadTask* subtask) { LoadTask* task = static_cast(subtask); boost::mutex::scoped_lock lock(_imageMutex); _images.push_back(task->getImage()); // More subtasks could be added at this point } void idle() { boost::mutex::scoped_lock lock(outMutex); std::cout << "All images loaded!" << std::endl; // More subtasks could be added at this point } void done() { boost::mutex::scoped_lock lock(outMutex); std::cout << "Main Task ended!" << std::endl; // No subtasks can be added now } private: const std::vector _files; boost::mutex _imageMutex; std::vector _images; }; int main(int argc, const char* argv[]) { ThreadPool::Ptr pool = ownedPtr(new ThreadPool(std::atoi(argv[1]))); std::vector files; for (int i = 2; i < argc; i++) files.push_back(argv[i]); MainTask task(pool, files); task.execute(); task.waitUntilDone(); std::cout << "Images loaded: " << task.getImages().size() << std::endl; return 0; }