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.
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.
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.
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.
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).
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
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: