Nasal Loops: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
m (Syntax highlight and remove (extraneous) heading)
Line 1: Line 1:
{{Nasal Navigation}}
{{Nasal Navigation}}
== Loops ==


Nasal has several ways to implement an iteration.
Nasal has several ways to implement an iteration.


===for, while, foreach, and forindex loops===
== for, while, foreach, and forindex loops ==
Nasal's looping constructs are mostly C-like:
Nasal's looping constructs are mostly C-like:


for(var i=0; i < 3; i = i+1) {
<syntaxhighlight lang="php">
  # loop body
for(var i=0; i < 3; i = i+1) {
  }
# loop body
}


while (condition) {
while (condition) {
  # loop body
  # loop body
}
}
</syntaxhighlight>


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:
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 ;
<syntaxhighlight lang="php">
foreach(elem; list1) { doSomething(elem); }  # NOTE: the delimiter is a SEMICOLON ;
</syntaxhighlight>


The hash/vector index expression is an lvalue that can be assigned as well as inspected:
The hash/vector index expression is an lvalue that can be assigned as well as inspected:


  foreach(light; lights) { lightNodes[light] = propertyPath; }
<syntaxhighlight lang="php">
foreach(light; lights) { lightNodes[light] = propertyPath; }
</syntaxhighlight>


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:
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 };
<syntaxhighlight lang="php">
foreach (var i; keys (myhash)) {
myhash= {first: 1000, second: 250, third: 25.2 };
  #multiply each value by 2:
foreach (var i; keys (myhash)) {
  myhash[i] *= 2;  
  #multiply each value by 2:
  #print the key and new value:
  myhash[i] *= 2;  
  print (i, ": ", myhash[i]);
  #print the key and new value:
}
  print (i, ": ", myhash[i]);
}
</syntaxhighlight>


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.
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]); }
<syntaxhighlight lang="php">
forindex(i; list1) { doSomething(list1[i]); }
</syntaxhighlight>


Also, braceless blocks work for loops equally well:
Also, braceless blocks work for loops equally well:


var c=0;
<syntaxhighlight lang="php">
while( c<5 )
var c=0;
while( c<5 )
   print( c+=1 );
   print( c+=1 );
print("end of loop\n");
print("end of loop\n");
</syntaxhighlight>


===settimer loops===
== settimer loops ==
Loops using <tt>while</tt>, <tt>for</tt>, <tt>foreach</tt>, and <tt>forindex</tt> 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.  
Loops using <tt>while</tt>, <tt>for</tt>, <tt>foreach</tt>, and <tt>forindex</tt> 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:  
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 {
<syntaxhighlight lang="php">
    print("this line appears once every two seconds");
var loop = func {
    settimer(loop, 2);
    print("this line appears once every two seconds");
}
    settimer(loop, 2);
}
loop();        # start loop
 
loop();        # start loop
</syntaxhighlight>


Note that the <tt>settimer</tt> function expects a ''function object'' (<tt>loop</tt>), not a function call (<tt>loop()</tt>) (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).  
Note that the <tt>settimer</tt> function expects a ''function object'' (<tt>loop</tt>), not a function call (<tt>loop()</tt>) (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).  
Line 62: Line 73:
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:
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:


<pre>
<syntaxhighlight lang="php">
var running = 1;
var running = 1;
var loop = func {
var loop = func {
    if (running) {
    if (running) {
        print("this line appears once every two seconds");
        print("this line appears once every two seconds");
        settimer(loop, 2);
        settimer(loop, 2);
    }
    }
}
}
 
loop();        # start loop ...
loop();        # start loop ...
...
...
running = 0;  # ... and let it die
running = 0;  # ... and let it die
</pre>
</syntaxhighlight>


== Loop Identifiers ==
=== 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.
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.
Line 99: Line 110:
Beginning with FlightGear 2.11+ you should consider using the '''maketimer()''' API instead.
Beginning with FlightGear 2.11+ you should consider using the '''maketimer()''' API instead.


[[List_of_Nasal_extension_functions#settimer.28.29|More information about the settimer function is below]]
[[List_of_Nasal_extension_functions#settimer.28.29|More information about the settimer function]]

Revision as of 15:25, 24 August 2013


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

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