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