IndexNextUpPreviousUrbi SDK 2.7.5

Chapter 3
Quick Start

This chapter presents Urbi SDK with a specific focus on its middleware features. It is self-contained in order to help readers quickly grasp the potential of Urbi used as a middleware. References to other sections of this document are liberally provided to point the reader to the more complete documentation; they should be ignored during the first reading.

 3.1 UObject Basics
  3.1.1 The Objects to Bind into Urbi
  3.1.2 Wrapping into an UObject
  3.1.3 Running Components
 3.2 Using urbiscript
  3.2.1 The urbiscript Scripting Language
  3.2.2 Concurrency
 3.3 Conclusion

3.1 UObject Basics

As a simple running example, consider a (very) basic factory. Raw material delivered to the factory is pushed into some assembly machines, which takes some time.

3.1.1 The Objects to Bind into Urbi

As a firth component of this factory, the core machine of the factory is implemented as follows. This class is pure regular C++, it uses no Urbi feature at all.

The header ‘machine.hh’, which declares Machine, is traditional. The documentation uses the Doxygen syntax.

 
#ifndef MACHINE_MACHINE_HH 
# define MACHINE_MACHINE_HH 
 
# include <urbi/uobject.hh> 
 
class Machine 
{ 
public: 
  /// Construction. 
  /// \param duration  how long the assembly process takes. 
  ///                  In seconds. 
  Machine(float duration); 
 
  /// Lists of strings. 
  typedef std::list<std::string> strings; 
 
  /// Assemble the raw components into a product. 
  std::string operator()(const strings& components) const; 
 
  /// The duration of the assembly process, in seconds. 
  /// Must be positive. 
  float duration; 
}; 
 
#endif // ! MACHINE_MACHINE_HH

The implementation file, ‘machine.cc’, is equally straightforward.

 
// A wrapper around Boost.Foreach. 
#include <libport/foreach.hh> 
 
#include "machine.hh" 
 
Machine::Machine(float d) 
  : duration(d) 
{ 
  assert(0 <= d); 
} 
 
std::string 
Machine::operator()(const strings& components) const 
{ 
  // Waiting for duration seconds. 
  useconds_t one_second = 1000 * 1000; 
  usleep(useconds_t(duration * one_second)); 
 
  // Iterate over the list of strings (using Boost.Foreach), and 
  // concatenate them in res. 
  std::string res; 
  foreach (const std::string& s, components) 
    res += s; 
  return res; 
}

3.1.2 Wrapping into an UObject

By binding a UObject, we mean using the UObject API to declare objects to the Urbi world. These objects have member variables (also known as attributes) and/or member functions (also known as methods) all of them or some of them being declared to Urbi.

One could modify the Machine class to make it a UObject, yet we recommend wrapping pure C++ classes into a different, wrapping, UObject. It is strongly suggested to aggregate the native C++ objects in the UObject — rather than trying to derive from it. By convention, we prepend a “U” to the name of the base class, hence the UMachine class. This class provides a simplified interface, basically restricted to what will be exposed to Urbi. It must derive from urbi::UObject.

 
#ifndef MACHINE_UMACHINE_HH 
# define MACHINE_UMACHINE_HH 
 
// Include the UObject declarations. 
# include <urbi/uobject.hh> 
 
// We wrap factories. 
# include "machine.hh" 
 
/// A UObject wrapping a machine object. 
class UMachine 
  : public urbi::UObject 
{ 
public: 
  /// C++ contructor. 
  /// \param name  name given to the instance. 
  UMachine(const std::string& name); 
 
  /// Urbi constructor. 
  /// \param d  the duration of the assembly process. 
  ///           Must be positive. 
  /// \return 0 on success. 
  int init(ufloat d); 
 
  /// Wrapper around Machine::operator(). 
  std::string assemble(std::list<std::string> components); 
 
  /// Function notified when the duration is changed. 
  /// \param v   the UVar being modified (i.e., UMachine::duration). 
  /// \return 0  on success. 
  int duration_set(urbi::UVar& v); 
 
private: 
  /// The duration of the assembly process. 
  urbi::UVar duration; 
 
  /// The actual machine, wrapped in this UObject. 
  Machine* machine; 
}; 
#endif // ! MACHINE_UMACHINE_HH

The implementation of UMachine is simple. It uses some of the primitives used in the binding process (Section 4.2):

Urbi relies on the prototype model for object-oriented programming, which is somewhat different from the traditional C++ class-based model (Section 8). This is reflected by the presence of two different constructors:

The following listing is abundantly commented, and is easy to grasp.

 
#include "umachine.hh" 
 
// Register the UMachine UObject in the Urbi world. 
UStart(UMachine); 
 
// Bouncing the name to the UObject constructor is mandatory. 
UMachine::UMachine(const std::string& name) 
  : urbi::UObject(name) 
  , machine(0) 
{ 
  // Register the Urbi constructor.  This is the only mandatory 
  // part of the C++ constructor. 
  UBindFunction(UMachine, init); 
} 
 
int 
UMachine::init(ufloat d) 
{ 
  // Failure on invalid arguments. 
  if (d < 0) 
    return 1; 
 
  // Bind the functions, i.e., declare them to the Urbi world. 
  UBindFunction(UMachine, assemble); 
  UBindThreadedFunctionRename 
    (UMachine, assemble, "threadedAssemble", urbi::LOCK_FUNCTION); 
  // Bind the UVars before using them. 
  UBindVar(UMachine, duration); 
 
  // Set the duration. 
  duration = d; 
  // Build the machine. 
  machine = new Machine(d); 
 
  // Request that duration_set be invoked each time duration is 
  // changed.  Declared after the above "duration = d" since we dont 
  // want it to be triggered for this first assignment. 
  UNotifyChange(duration, &UMachine::duration_set); 
 
  // Success. 
  return 0; 
} 
 
int 
UMachine::duration_set(urbi::UVar& v) 
{ 
  assert(machine); 
  ufloat d = static_cast<ufloat>(v); 
  if (0 <= d) 
  { 
    // Valid value. 
    machine->duration = d; 
    return 0; 
  } 
  else 
    // Report failure. 
    return 1; 
} 
 
 
std::string 
UMachine::assemble(std::list<std::string> components) 
{ 
  assert(machine); 
 
  // Bounce to Machine::operator(). 
  return (*machine)(components); 
}

3.1.3 Running Components

As a first benefit from using the Urbi environment, this source code is already runnable! No main function is needed, the Urbi system provides one.

3.1.3.1 Compiling

Of course, beforehand, we need to compile this UObject into some loadable module. The Urbi modules are shared objects, i.e., libraries that can be loaded on demand (and unloaded) during the execution of the program. Their typical file names depend on the architecture: ‘machine.so’ on most Unix platforms (including Mac OS X), and ‘machine.dll’ on Windows. To abstract away from these differences, we will simply use the base name, ‘machine’ with the Urbi tool chain.

There are several options to compile our machine as a UObject. If you are using Microsoft Visual Studio, the Urbi SDK installer created a “UObject” project template accessible through the “New project” wizard. Otherwise, you can use directly your regular compiler tool chain. You may also use umake-shared from the ‘umake-*’ family of programs (Section 21.10.2):

 
$ ls machine.uob 
machine.cc  machine.hh  umachine.cc  umachine.hh 
$ umake-shared machine.uob -o machine 
# ... Lots of compilation log messages ... 
$ ls 
_ubuild-machine.so  machine.la  machine.so  machine.uob  

The various files are:

machine.uob
Merely by convention, the sources of our UObject are in a ‘*.uob’ directory.
machine.so
A shared dlopen-module. This is the “true” product of the umake-shared invocation. Its default name can be quite complex (‘uobject-i386-apple-darwin9.7.0.so’ on my machine), as it will encode information about the architecture of your machine; if you don’t need such accuracy, use the option ‘--output’/‘-o’ to specify the output file name.

umake-shared traversed ‘machine.uob’ to gather and process relevant files (source files, headers, libraries and object files) in order to produce this output file.

_ubuild-machine.so
this is a temporary directory in which the compilation takes place. It can be safely removed by hand, or using umake-deepclean (Section 21.10.2).
machine.la
a GNU Libtool file that contains information such as dependencies on other libraries. While this file should be useless most of the time, we recommend against removing it: it may help understand some problems.

3.1.3.2 Running UObjects

There are several means to toy with this simple UObject. You can use urbi-launch (Section 21.5) to plug the UMachine in an Urbi server and enter an interactive session.

 
# Launch an Urbi server with UMachine plugged in. 
$ urbi-launch --start machine -- --interactive 
[00000000] *** ******************************************************** 
[00000000] *** Urbi SDK version 2.7 rev. a6a1ec5 
[00000000] *** Copyright (C) 2004-2011 Gostai S.A.S. 
[00000000] *** 
[00000000] *** This program comes with ABSOLUTELY NO WARRANTY.  It can 
[00000000] *** be used under certain conditions.  Type ‘license;’, 
[00000000] *** ‘authors;’, or ‘copyright;’ for more information. 
[00000000] *** 
[00000000] *** Check our community site: http://www.urbiforge.org. 
[00000000] *** ******************************************************** 
var f = UMachine.new(1s); 
[00020853] UMachine_0x1899c90 
f.assemble(["Hello, ""World!"]); 
[00038705] "Hello, World!" 
shutdown;  

You may also launch the machine UObject in the background, as a network component:

 
$ urbi-launch --start machine --host 0.0.0.0 --port 54000 &  

and interact with it using your favorite client (telnet, netcat, socat, …), or using the urbi-send tool (Section 21.8).

 
$ urbi-send --port 54000                       \ 
            -e ’UMachine.assemble([12, 34]);’  \ 
            --quit 
[00126148] "1234" 
[00000000:client_error] End of file 
$ urbi-send --port 54000                     \ 
            -e ’var f = UMachine.new(1s)|’   \ 
            -e ’f.assemble(["ab", "cd"]);’   \ 
            --quit 
[00146159] "abcd" 
[00000000:client_error] End of file  

3.2 Using urbiscript

urbiscript is a programming language primarily designed for robotics. Its syntax is inspired by that of C++: if you know C, C++, Java or C#, writing urbiscript programs is easy. It’s a dynamic object-oriented (Listing 11) scripting language, which makes it well suited for high-level application. It supports and emphasizes parallel (Listing 13) and event-based programming (Listing 14), which are very popular paradigms in robotics, by providing core primitives and language constructs.

Thanks to its client/server approach, one can easily interact with a robot, to monitor it, or to experiment live changes in the urbiscript programs.

Courtesy of the UObject architecture, urbiscript is fully integrated with C++. As already seen in the above examples (Section 3.1.3), you can bind C++ classes in urbiscript seamlessly. urbiscript is also integrated with many other languages such as Java (Listing 5), MatLab or Python. The UObject framework also naturally provides urbiscript with support for distributed architectures: objects can run in different processes, possibly on remote computers.

3.2.1 The urbiscript Scripting Language

The following example shows how one can easily interface UObjects into the urbiscript language. The following simple class (actually, a genuine object, in urbiscript “classes are objects”, see Listing 11) aggregates two assembly machines, a fast one, and a slow one. This class demonstrates usual object-oriented, sequential, features.

 
class TwoMachineFactory 

  // A shorthand common to all the Two Machine factories. 
  var UMachine = uobjects.UMachine; 
 
  // Default machines. 
  var fastMachine = UMachine.new(10ms); 
  var slowMachine = UMachine.new(100ms); 
 
  // The urbiscript constructor. 
  // Build two machines, a fast one, and a slow one. 
  function init(fast = 10ms, slow = 100ms) 
  { 
    // Make sure fast <= slow. 
    if (slow < fast) 
      [fast, slow] = [slow, fast]; 
    // Two machines for each instance of TwoMachineFactory. 
    fastMachine = UMachine.new(fast); 
    slowMachine = UMachine.new(slow); 
  }; 
 
  // Wrappers to make invocation of the machine simpler. 
  function fast(input) { fastMachine.assemble(input) }; 
  function slow(input) { slowMachine.assemble(input) }; 
 
  // Use the slow machine for large jobs. 
  function assemble(input) 
  { 
    var res| 
    var machine| 
    if (5 < input.size) 
      { machine = "slow" | res = slow(input); } 
    else 
      { machine = "fast" | res = fast(input); } | 
    echo ("Used the %s machine (%s => %s)" % [machine, input, res]) | 
    res 
  }; 
}; 
[00000001] TwoMachineFactory  

Using this class is straightforward.

 
var f = TwoMachineFactory.new|; 
f.assemble([1, 2, 3, 4, 5, 6]); 
[00000002] *** Used the slow machine ([1, 2, 3, 4, 5, 6] => 123456) 
[00000003] "123456" 
f.assemble([1]); 
[00000004] *** Used the fast machine ([1] => 1) 
[00000005] "1"  

The genuine power of urbiscript is when concurrency comes into play.

3.2.2 Concurrency

Why should we wait for the slow job to finish if we have a fast machine available? To do so, we must stop requesting a sequential composition between both calls. We did that by using the sequential operator, ‘;’. In urbiscript, there exists its concurrent counter-part: ‘,’. Indeed, running ab means “launch the program a and then launch the program b”. The urbiscript Manual contains a whole section devoted to explaining these operators (Listing 13).

3.2.2.1 First Attempt

Let’s try it:

 
f.assemble([1, 2, 3, 4, 5, 6]), 
f.assemble([1]), 
[00000002] *** Used the slow machine ([1, 2, 3, 4, 5, 6] => 123456) 
[00000004] *** Used the fast machine ([1] => 1)  

This is a complete failure.

Why?

Since Urbi cannot expect that your code is thread-safe, by default all calls to UObject features are synchronous, or blocking: the whole Urbi system is suspended until the function returns. There is a single thread of execution for Urbi, and when calling a function from a (plugged) UObject, that thread of execution is devoted to evaluated the code.

See for instance below that, in even though f.assemble is slow and launched in background, the almost instantaneous display of ping is not performed immediately.

 
echo(f.slow([1, 2, 3, 4, 5, 6])), 
echo("ping"); 
[00000002] *** 123456 
[00000002] *** ping  

There are several means to address this unintended behavior. If the base library provides a threaded API (in our example, the Machine class, not the UMachine UObject wrapper), then you could use it. Yet we don’t recommend it, as it takes away from Urbi the possibility to really control concurrency issues (for instance it cannot turn non-blocking functions into blocking functions).

A better option is to ask Urbi to turn blocking function calls into non-blocking ones.

3.2.2.2 Second Attempt: Threaded Functions

If you read carefully the body of the UMachine::init function, you will find the following piece of code:

 
UBindFunction(UMachine, assemble); 
UBindThreadedFunctionRename 
  (UMachine, assemble, "threadedAssemble", urbi::LOCK_FUNCTION);  

Both calls bind the function UMachine::assemble, but the second one will run the call in a separate thread. Since multiple calls to a single function (or different functions of a single object etc.) are likely to fail, a locking model must be provided. Here, urbi::LOCK_FUNCTION means that concurrent calls to UMachine::assemble must be serialized: one at a time.

To use this threaded version of assemble, we can simply patch our TwoMachineFactory class:

 
do (TwoMachineFactory) 

  fast = function (input) { fastMachine.threadedAssemble(input) }; 
  slow = function (input) { slowMachine.threadedAssemble(input) }; 
}|;  

Let’s try again where we failed previously (Section 3.2.2.1):

 
f.assemble([1, 2, 3, 4, 5, 6]), 
f.assemble([1]), 
[00000004] *** Used the fast machine ([1] => 1) 
[00000002] *** Used the slow machine ([1, 2, 3, 4, 5, 6] => 123456) 
sleep(200ms);  

Victory! The fast machine answered first.

You may have noticed that the result is no longer reported. Indeed, the urbiscript interactive shell only displays the result of synchronous expressions (i.e., those ending with a ‘;’): asynchronous answers are confusing (see the inversion here).

Channels are useful to “send” asynchronous answers.

 
var c1 = Channel.new("c1")|; 
var c2 = Channel.new("c2")|; 
c1 << f.assemble([10, 20, 30, 40, 50, 60]), 
c2 << f.assemble([100]), 
sleep(200ms); 
[00000535] *** Used the fast machine ([100] => 100) 
[00000535:c2] "100" 
[00000625] *** Used the slow machine ([10, 20, 30, 40, 50, 60] => 102030405060) 
[00000625:c1] "102030405060"  

3.3 Conclusion

This section gave only a quick glance to all the power that Urbi provides to support concurrency. Actually, it makes plenty of sense to embed an Urbi engine in a native C++ program, and to delegate the concurrency issues to it. Thanks to its middleware and client/server architecture, it is then possible to connect it to remote components of different kinds, such as using ROS for instance.

Then, a host of features are at hand, ready to be used when you need them: event-driven programming, automatic monitoring of expressions, interactive sessions, …and, last but not least, the urbiscript programming language.