Nasal Loops: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
(Template usage)
m (Update {{func link}} form)
Line 5: Line 5:
A polling loop is akin to somebody permanently running to a room to check if the lights are on - a listener is like somebody being INSIDE the room SLEEPING and only WAKING up once the lights are turned on.
A polling loop is akin to somebody permanently running to a room to check if the lights are on - a listener is like somebody being INSIDE the room SLEEPING and only WAKING up once the lights are turned on.


The {{func link|setlistener}} API is intended to catch rare events. Avoid complex loops if you don't have to.
The {{func link|setlistener()}} API is intended to catch rare events. Avoid complex loops if you don't have to.


In general, "loops" are not bad or expensive, it really depends on what you're doing inside the loop.
In general, "loops" are not bad or expensive, it really depends on what you're doing inside the loop.
Line 13: Line 13:
timers or listeners are only really preferable over loops when it comes to checking for some condition, because polling is called "busy-waiting", i.e. more expensive, see my previous analogy.
timers or listeners are only really preferable over loops when it comes to checking for some condition, because polling is called "busy-waiting", i.e. more expensive, see my previous analogy.
A listener or timer "waiting" is not resource-hungry, it's not even busy - it's not doing anything until it is "fired".
A listener or timer "waiting" is not resource-hungry, it's not even busy - it's not doing anything until it is "fired".
Regarding {{func link|setprop}}/{{func link|getprop}} - they're not as bad as we used to think - in fact, Thorsten has shown that they're preferable over most [[Nasal library/props|props.nas APIs}}, this may however change once the whole thing is replaced with cppbind bindings.
Regarding {{func link|setprop()}}/{{func link|getprop()}} - they're not as bad as we used to think - in fact, Thorsten has shown that they're preferable over most [[Nasal library/props|props.nas APIs}}, this may however change once the whole thing is replaced with cppbind bindings.


Well loops aren't bad necessarily: they can be used in a less-than-optimal manner, but there are often times where they make a lot of sense. Some pros and cons of both:
Well loops aren't bad necessarily: they can be used in a less-than-optimal manner, but there are often times where they make a lot of sense. Some pros and cons of both:
Line 35: Line 35:
{{Note|Need to discuss split-frame-loops here!}}
{{Note|Need to discuss split-frame-loops here!}}


To optimize things in Nasal space, you need to understand where things really ARE slow - for starters, you can use "debug.benchmark()" for this - which is wrapper for two {{func link|systime}} calls to capture the overhead of the callback.
To optimize things in Nasal space, you need to understand where things really ARE slow - for starters, you can use "debug.benchmark()" for this - which is wrapper for two {{func link|systime()}} calls to capture the overhead of the callback.


Philosopher has some more sophisticated stuff doing this sort of thing, but it's more difficult to set up and not yet ready for "prime-time" - overall, discussions like these make it obvious that we could greatly benefit from having support for runtime benchmarking/profiling and debugging of Nasal code running within the FG main loop.
Philosopher has some more sophisticated stuff doing this sort of thing, but it's more difficult to set up and not yet ready for "prime-time" - overall, discussions like these make it obvious that we could greatly benefit from having support for runtime benchmarking/profiling and debugging of Nasal code running within the FG main loop.
Line 156: Line 156:
</syntaxhighlight>
</syntaxhighlight>


Note that the {{func link|settimer}} 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 {{func link|settimer()}} 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).  


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:
Line 195: Line 195:
</syntaxhighlight>
</syntaxhighlight>


Beginning with FlightGear 2.11+ you should consider using the {{func link|maketimer}} API instead.
Beginning with FlightGear 2.11+ you should consider using the {{func link|maketimer()}} API instead.


See also {{func link|settimer}}
See also {{func link|settimer()}}

Revision as of 15:35, 16 November 2016


Nasal has several ways to implement an iteration.

A polling loop is akin to somebody permanently running to a room to check if the lights are on - a listener is like somebody being INSIDE the room SLEEPING and only WAKING up once the lights are turned on.

The setlistener() API is intended to catch rare events. Avoid complex loops if you don't have to.

In general, "loops" are not bad or expensive, it really depends on what you're doing inside the loop. A loop will be executed within a single frame normally - so a long-running loop will add up to the frame spacing. There's nothing magic about timers or listeners - they can just as well inflate your frame spacing. It doesn't matter if the code/callback is run inside a loop, timer or a listener - what matter is the complexity of the code that runs. timers or listeners are only really preferable over loops when it comes to checking for some condition, because polling is called "busy-waiting", i.e. more expensive, see my previous analogy. A listener or timer "waiting" is not resource-hungry, it's not even busy - it's not doing anything until it is "fired". Regarding setprop() /getprop() - they're not as bad as we used to think - in fact, Thorsten has shown that they're preferable over most [[Nasal library/props|props.nas APIs}}, this may however change once the whole thing is replaced with cppbind bindings.

Well loops aren't bad necessarily: they can be used in a less-than-optimal manner, but there are often times where they make a lot of sense. Some pros and cons of both:

Listeners: Pros:

  • Can be used to receive instant "notifications" (events), avoiding unnecessary gets,
  • Really useful for Updating another property based on changes in one - like a mirror that scales by a factor, or something. (It's kinda a pity we can't just redirect read/writes... That's something I haven't explored enough, since it's various parts of C++)

Listeners: Cons:

  • Each event is run in the same code as the event that triggers it (aka the setting-a-property code calls the event), so each event is run in the same thread and has the possibility to block the parent - which is probably not a good idea, so listeners should generally run as little code as possible. Note however that, depending on the property, it's all going to get run inside the main loop anyway - so it's no different than loops (roughly speaking).
  • May be run several times per frame.
  • Little more abstract and prone to danger than loops; you do not always know what listeners are registered where, and any listener that writes to a different property has the potential to infinitely call each other and thus segfault. (Unless you make guards on your listener against this... which is possible, but requires some work.)

Loops: Pros:

  • Very useful for running loop-like tasks - e.g. something that updates regularly, particularly if it looks at many properties, or if it changes even if the dependent-upon property(s) are not updated.
  • Run once a frame at max – which is the fastest the user will see the changes anyway.

Loops: Cons:

  • However, they are run at the end (IIRC) of the frame, so if you need instant reaction (i.e. interaction back and forth with another subsystem via properties), loops won't be quick enough, and thus listeners would be required.
Note  Need to discuss split-frame-loops here!

To optimize things in Nasal space, you need to understand where things really ARE slow - for starters, you can use "debug.benchmark()" for this - which is wrapper for two systime() calls to capture the overhead of the callback.

Philosopher has some more sophisticated stuff doing this sort of thing, but it's more difficult to set up and not yet ready for "prime-time" - overall, discussions like these make it obvious that we could greatly benefit from having support for runtime benchmarking/profiling and debugging of Nasal code running within the FG main loop. Given Philosopher's progress in 2013, I think we're at least half-way there already - but it's not a trivial problem, and it needs still some more work. We're hoping to look into this again this year - anybody interested in helping with this, should check out Philosopher's "Nasal internals" document in $FG_ROOT/Docs.

Nasal loops are 'worse' than C++ loops since the former tend to trigger garbage collection more often, which leads in the worst case to an artificially prolonged frame. Some people claim to have had serious issues with Nasal GC.

It is entirely true that ANY Nasal code, including loops, may end up triggering the GC - even due to code unrelated to the loop itself, what we commonly referred to as "GC pressure", i.e. due to certain coding constructs being more likely to add to the "GC pressure".

C++ loops should normally not trigger the Nasal GC at all, unless the C++ code happens to call back into Nasal space. Normally, there's a vector of subsystems that's iterated in the FG main loop, each subsystem's update() method is then called, including the update() method of the Nasal subsystem. However, most Nasal code is not directly called via Nasal::update(), but instead via callbacks by other C++ subsystems, such as events (for timers) or the property tree code (for listeners), but also the AI traffic system, scenery system - or even the GUI system, whenever Nasal hooks are supported by the system, the corresponding system needs to reach back into Nasal space, which may end up triggering the GC.

regarding callbacks invoked via timers and listeners, I would also suggest to always maintain a counter to see how many instances of each loop are running - i.e. initialize the counter with 0, and then use +=1 and -=1 at the end of the callback.

you can then easily dump all counters to the console via print In the meantime what you could do is keep taking things out until you find some things that slow FG down. Start with the 3D model (eg replace with UFO) then take out various parts of Nasal, particularly those that have timers, listeners, or other callbacks. From there you can narrow it down by checking individual call times via systime(), like my framework does (on a large scale), or maybe debug.benchmark when I provide a small patch...


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

skip or exit loops prematurely

If you want to skip a single loop use "continue" or to exit a loop prematurely use "break".

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

    # don't do loop for i == 3
    if ( i == 3 ) continue;

    # exit for other reason
    if ( want_to_quit ) break;
}

settimer loops

Caution
Cquote1.png it's relatively easy to do bad things unintentionally. Like tie a bit of code to an FDM property and run updates of a display 120 times per second rather than the 30 times you actually need. Like start a loop multiple times so that you update the same property 30 times per frame rather than the one time you actually need. It's actually pretty hard to catch these things, because the code is formally okay, does the right thing and just eats more performance than necessary, and there's no simple output telling you that you're running 30 loops rather than the one you expect.
Cquote2.png

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.

See also settimer()