IndexNextUpPreviousUrbi SDK 2.7.5

Chapter 14
Event-based Programming

When dealing with highly interactive agent programming, sequential programming is inconvenient. We want to react to external, random events, not execute code linearly with a predefined flow. urbiscript has a strong support for event-based programming.

 14.1 Watchdog constructs
 14.2 Events
  14.2.1 Emitting Events
  14.2.2 Emitting events with a payload

14.1 Watchdog constructs

The first construct we will study uses the at keyword. Given a condition and a statement, at will evaluate the statement each time the condition becomes true. That is, when a rising edge occurs on the condition.

 
var x = 0; 
[00000000] 0 
at (x > 5) 
  echo("ping"); 
x = 5; 
[00000000] 5 
// This triggers the event. 
x = 6; 
[00000000] 6 
[00000000] *** ping 
// Does not trigger, since the condition is already true. 
x = 7; 
[00000000] 7 
// The condition becomes false here. 
x = 3; 
[00000000] 3 
 
x = 10; 
[00000000] 10 
[00000000] *** ping  

An onleave block can be appended to execute an expression when the expression becomes false — that is, on falling edges.

 
var x = false; 
[00000000] false 
at (x) 
  echo("x"
onleave 
  echo("!x"); 
x = true; 
[00000000] true 
[00000000] *** x 
x = false; 
[00000000] false 
[00000000] *** !x  

See Listing 22.10.1 for more details on at statements.

The whenever construct is similar to at, except the expression evaluation is systematically restarted when it finishes as long as the condition stands true.

 
var x = false; 
[00000000] false 
whenever (x) 

  echo("ping"); 
  sleep(1s); 
}; 
x = true; 
[00000000] true 
sleep(3s); 
// Whenever keeps triggering 
[00000000] *** ping 
[00001000] *** ping 
[00002000] *** ping 
x = false; 
[00002000] false 
// Whenever stops triggering  

Just like at has onleave, whenever has else: the given expression is evaluated as long as the condition is false.

 
var x = false; 
[00002000] false 
whenever (x) 

  echo("ping"); 
  sleep(1s); 

else 

  echo("pong"); 
  sleep(1s); 
}; 
sleep (3s); 
[00000000] *** pong 
[00001000] *** pong 
[00002000] *** pong 
x = true; 
[00003000] true 
sleep (3s); 
[00003000] *** ping 
[00004000] *** ping 
[00005000] *** ping 
x = false; 
[00006000] false 
sleep (2s); 
[00006000] *** pong 
[00007000] *** pong  

14.2 Events

In addition to monitoring an expression with a watchdog, urbiscript enables you to define events that can be caught with the at and whenever constructs we saw earlier. You can create events by instantiating the Event prototype. They can then be emitted with the ! keyword.

14.2.1 Emitting Events

 
var myEvent = Event.new; 
[00000000] Event_0xb5579008 
at (myEvent?) 
  echo("ping"); 
myEvent!; 
[00000000] *** ping 
// events work well with parallelism 
myEvent! & myEvent!; 
[00000000] *** ping 
[00000000] *** ping  

Both at and whenever have the same behavior on punctual events. However, if you emit an event for a given duration, whenever will keep triggering for this duration, contrary to at.

 
var myEvent = Event.new; 
[00000000] Event_0xb558a588 
whenever (myEvent?) 

  echo("ping (whenever)")| 
  sleep(200ms) 
}; 
at (myEvent?) 

  echo("ping (at)")| 
  sleep(200ms) 
}; 
// Emit myEvent for .3 second. 
myEvent!  300ms; 
[00000000] *** ping (whenever) 
[00000100] *** ping (whenever) 
[00000000] *** ping (at)  

14.2.2 Emitting events with a payload

Events behave very much like “channels”: listeners use at or whenever, and producers use !. Fortunately, the messages can include a payload, i.e., something sent in the “message”. The Event then behaves very much like an identifier of the message type. To send/catch the payload, just pass arguments to ! and ?:

 
var event = Event.new; 
[00000000] Event_0x0 
 
at (event?(var payload)) 
  echo("received: " + payload) 
onleave 
  echo("had received: " + payload); 
 
event!(1); 
[00000008] *** received: 1 
[00000009] *** had received: 1 
 
event!(["string", 124]); 
[00000010] *** received: ["string", 124] 
[00000011] *** had received: ["string", 124]  

Like functions, events have an arity, i.e., they depend on the number of arguments: at (event?(arg)) will only match emissions whose payload contain exactly one argument, i.e., event!(arg).

 
// Too many arguments. 
event!(1, 2); 
 
// Not enough arguments. 
event!; 
event!();  

Event handlers that do not specify their arity (i.e., without parentheses) match event emissions of any arity.

 
at (event?) 
  echo("received an event"
onleave 
  echo("had received an event"); 
 
event!; 
[00000014] *** received an event 
[00000015] *** had received an event 
 
event!(1); 
[00000016] *** received: 1 
[00000017] *** had received: 1 
[00000018] *** received an event 
[00000019] *** had received an event 
 
event!(1, 2); 
[00000020] *** received an event 
[00000021] *** had received an event  

Actually, the feature is much more powerful than this: full pattern matching applies, as with the switch/case construct.

 
var e = Event.new|; 
 
at (e?) 
  echo("e"); 
 
at (e?(var x)) 
  echo("e(x)"); 
 
at (e?(1)) 
  echo("e(1)"); 
 
at (e?(var x) if x.isA(Float) && x % 2) 
  echo("e(odd)"); 
 
// Payload must be a list of three members, the first two being 1 and 2, and 
// the third one being greater than 2, when converted as a Float. 
at (e?([1, 2, var x]) if 2 < x.asFloat) 
  echo("e([1, 2, x = %s])" % x); 
 
e!; 
[00000845] *** e 
 
e!(0); 
[00011902] *** e 
[00011902] *** e(x) 
 
e!(1); 
[00023327] *** e 
[00023327] *** e(x) 
[00023327] *** e(1) 
[00023327] *** e(odd) 
 
e!([1, 2, 1]); 
[00024327] *** e 
[00024327] *** e(x) 
 
e!([1, 2, 3]); 
[00025327] *** e 
[00025327] *** e(x) 
[00025327] *** e([1, 2, x = 3]) 
 
e!([1, 2, "4"]); 
[00026327] *** e 
[00026327] *** e(x) 
[00026327] *** e([1, 2, x = 4])