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(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, 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 ;

The hash/vector index expression 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, for 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]);

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, 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

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);

More information about the settimer function is below