Nasal Loops: Difference between revisions

Jump to navigation Jump to search
2,558 bytes added ,  5 January 2019
m (Update {{func link}} form)
(6 intermediate revisions by 2 users not shown)
Line 3: Line 3:
Nasal has several ways to implement an iteration.
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.
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 (there is no active workload involved here, other than basically "watching" the switch).


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.
A loop will be executed within a single frame normally - so a long-running loop will add up to the frame spacing.
A loop will be executed within a single frame normally - so a long-running loop will add up to the frame spacing (the time needed to create a single frame).
 
There's nothing magic about timers or listeners - they can just as well inflate your 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.
It doesn't matter if the code/callback is run inside a loop, timer or a listener - what does 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 the 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 [[Nasal/CppBind|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:
Thus, 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:
Listeners: Pros:
Line 142: Line 146:
== settimer loops ==
== settimer loops ==
{{Callback Disclaimer}}
{{Callback Disclaimer}}
{{warning|settimer loops are deprecated as of FlightGear 2.11, see the [[#maketimer]] section at the end of this document}}


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 for the duration of the loop body, 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 for the duration of the loop body, and can, thus, only be used for instantaneous operations that don't take too long.  
Line 160: Line 166:
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:


{{warning|settimer loops are deprecated as of FlightGear 2.11, see the [[#maketimer]] section at the end of this document}}
<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
var running = 1;
var running = 1;
Line 176: Line 183:
=== 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.
The example above has a fundamental problem; which is that it isn't possible to manage the timer as it will always fire (which is why it is better to use [[#maketimer]]). However the problem can also be solved by using a loop identifier. This is simply a variable that lives at the outer scope of the module and is used to identify the currently active timer loop. Using this technique when the loop identifier is incremented the currently waiting timers will exit when it is their turn to run and the timer chain will be broken.  


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.
To do this we provide each loop chain with a ''loop identifier'' and let the loop chain function will terminate itself at the start if the id doesn't match the loop-id from the outer loop. To facilitate this also requires a delegate in the settimer call that will pass in the current loop id (that was passed into the loop chain function).  
 
==== Example of using the loop indentifier technique ====
 
{{warning|settimer loops are deprecated as of FlightGear 2.11, see the [[#maketimer]] section at the end of this document}}


<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
Line 194: Line 205:
  loop(loopid);      # start new chain; this can also be abbreviated to:  loop(loopid += 1);
  loop(loopid);      # start new chain; this can also be abbreviated to:  loop(loopid += 1);
</syntaxhighlight>
</syntaxhighlight>
== Starting timers from a fdm initialized listener ==
{{caution|Be carfeul when using settimer from a listener on fdm-initialized as this can easily create a new timer each time the aircraft is repositioned}}
The correct way to use settimer from a listener on fdm-initialized is as follows:
{{warning|settimer loops are deprecated as of FlightGear 2.11, see the [[#maketimer]] section at the end of this document}}
<syntaxhighlight lang="nasal">
good_loop_init = 0;
good_loop = func{
# logic
    settimer(good_loop, 1);
};
setlistener("sim/signals/fdm-initialized", func {
    if (!good_loop_init){
      good_loop_init = 1;
      good_loop();
    }
});
</syntaxhighlight>
== maketimer ==


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()}}
This is because maketimer creates an object that can be managed, rather than settimer which once called will irrevocably call the method once the duration has elapsed. For this reason settimer is considered to be unmanageable and has been deprecated.
 
=== maketimer example ===
 
<syntaxhighlight lang="nasal">
var mt_loop_example = func {
 
    if (some_condition)
      mt_loop_exampleTimer.restart(0.1); # adjust the timer frequency (ms)
 
    if (finished)
      mt_loop_exampleTimer.stop(); #cancel the timer. Timer can be started again later.
}
 
mt_loop_exampleTimer = maketimer(0.25, mt_loop_example);
mt_loop_exampleTimer.simulatedTime = 1; # use simulated time, as maketimer defaults to using wallclock time and continues during pause.
mt_loop_exampleTimer.start();
</syntaxhighlight>
 
=== maketimer from FDM Initialized ===
 
<syntaxhighlight lang="nasal">
 
var timer_loop = func{
# logic
};
 
timer_loopTimer = maketimer(0.25, timer_loop);
 
setlistener("sim/signals/fdm-initialized", func {
    timer_loopTimer.start();
});
</syntaxhighlight>
 
See also {{func link|maketimer()}} {{func link|settimer()}}
308

edits

Navigation menu