IndexNextUpPreviousUrbi SDK 3.0.0

Chapter 11
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.

 11.1 Watchdog constructs
 11.2 Events
  11.2.1 Emitting Events
  11.2.2 Emitting events with a payload
  11.2.3 waituntil

11.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 20.11.1 for more details on at statements.

11.2 Events

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

11.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  

myEvent! triggers an instantaneous event, which has no effect on at registered after it:

 
var ev = Event.new(); 
[00000000] Event_0xb5579008 
ev!; 
at(ev?) echo("ping"); 
// Nothing happens unless ev is emitted again.  

Events can be emited for a given duration using the (duration) construct after the ! :

 
var event = Event.new(); 
[00000000] Event_0xb5579008 
// emit event for 1 second in parallel with the scope 
event!(1s), 
// Use an at 100ms later, the event is still in triggered state. 
sleep(100ms) | at(event?) echo("ping"); 
sleep(1s); 
[00000000] *** ping  

Note in the example above that at only triggers once for the whole duration of the emit.

11.2.2 Emitting events with a payload

Events behave very much like “channels”: listeners use at, 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_0x00000001 
 
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])  

Remember stopif10.4.2 and freezeif10.4.2 from the previous section? They also accept an event in their condition:

 
var e = Event.new()|; 
var counter = 3|; 
stopif(e?(0)) while(true) { echo(counter); e!(counter); counter--;}; 
[00026327] *** 3 
[00026327] *** 2 
[00026327] *** 1 
[00026327] *** 0  

11.2.3 waituntil

Sometimes it is useful to wait until a condition becomes true, or an event triggers. This task can be accomplished with the waituntil20.11.2 construct:

 
counter = 0|; 
waituntil(counter == 2) | echo("Now!"), // in parallel 
for (var i: 4) { echo(i); counter = i}; 
[00000001] *** 0 
[00000001] *** 1 
[00000001] *** 2 
[00000001] *** Now! 
[00000001] *** 3