Nasal Loops

From FlightGear wiki
Jump to navigation Jump to search

Nasal has several ways to implement an iteration.

for, while, foreach, and forindex loops

Nasal's looping constructs are mostly C-like:

for( preloop_initialization; # will be run prior to the first invocation of the loop, usually to initialize a loop counter
     condition_during_loop;  # will be run prior to each iteration, usually to check the loop counter  and cancel the loop if false
     post_iteration_expression # will be run after each iteration, usually to increment a loop counter
    # loop body

for (var i=0; i < 3; i = i+1) {
    # loop body

while (condition) {
    # loop body

The differences are that there is no do{}while(); construct, and there is a foreach/forindex, which takes a local variable name as its first argument and a vector as its second:

foreach(elem; list1) { doSomething(elem); }  # NOTE: the delimiter is a SEMICOLON ;

In other words, even though foreach or forindex look like function calls, they work differently and they have braces after the parentheses.

The hash/vector index expression (one that uses brackets) is an lvalue that can be assigned as well as inspected:

foreach(light; lights) { lightNodes[light] = propertyPath; }

To walk through all elements of a hash, use a foreach loop on the keys of they hash. Then you call pull up the values of the hash using the key. Example:

myhash = {first: 1000, second: 250, third: 25.2 };
foreach (var i; keys (myhash)) {
  #multiply each value by 2:
  myhash[i] *= 2; 
  #print the key and new value:
  print (i, ": ", myhash[i]);
# this will print in some order:
#first: 2000
#second: 250
#thid: 25.2

There is also a "forindex", which is like foreach except that it assigns the index of each element, instead of the value, to the loop variable.

forindex(i; list1) { doSomething(list1[i]); }

Also, braceless blocks work for loops equally well:

var c = 0;
while( c < 5 )
  print( c += 1 );
print("end of loop\n");

settimer loops

Loops using while, for, foreach, and forindex block all of FlightGear's subsystems that run in the main thread for the duration of the loop body, and can, thus, only be used for instantaneous operations that don't take too long.

For operations that should continue over a longer period, one needs a non-blocking solution. This is done by letting functions call themselves after a timed delay:

var loop = func {
    print("this line appears once every two seconds");
    settimer(loop, 2);

loop();        # start loop

Note that the settimer function expects a function object (loop), not a function call (loop()) (though it is possible to make a function call return a function object--an advanced functional programming technique that you won't need to worry about if you're just getting started with Nasal).

The fewer code FlightGear has to execute, the better, so it is desirable to run loops only when they are needed. But how does one stop a loop? A once triggered timer function can't be revoked. But one can let the loop function check an outside variable and refuse calling itself, which makes the loop chain die off:

var running = 1;
var loop = func {
    if (running) {
        print("this line appears once every two seconds");
        settimer(loop, 2);

loop();        # start loop ...
running = 0;   # ... and let it die

Loop Identifiers

Unfortunately, this method is rather unreliable. What if the loop is "stopped" and a new instance immediately started again? Then the running variable would be 1 again, and a pending old loop call, which should really finish this chain, would happily continue. And the new loop chain would start, too, so that we would end up with two loop chains.

This can be solved by providing each loop chain with a loop identifier and letting the function end itself if the id doesn't match the global loop-id. Self-called loop functions need to inherit the chain id. So, every time the global loop id is increased, all loop chains die, and a new one can immediately be started.

 var loopid = 0;
 var loop = func(id) {
     id == loopid or return;           # stop here if the id doesn't match the global loop-id
     settimer(func { loop(id) }, 2);   # call self with own loop id
 loop(loopid);       # start loop
 loopid += 1;        # this kills off all pending loops, as none can have this new identifier yet
 loop(loopid);       # start new chain; this can also be abbreviated to:  loop(loopid += 1);

Beginning with FlightGear 2.11+ you should consider using the maketimer() API instead.

More information about the settimer function