List of Nasal extension functions: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
mNo edit summary
(Redirected page to Nasal library#Extension functions)
 
(22 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{Template:Nasal Navigation}}
#REDIRECT [[Nasal_library#Extension_functions]]
{{Out of date|The accuracy of this article or section may be compromised due to recent changes in the source code, please help us update this article by incorporating the [http://mapserver.flightgear.org/git/?p=flightgear;a=blob;f=src/Scripting/NasalSys.cxx#l707 latest extension functions]}}
 
 
== FlightGear extension functions ==
 
=== <tt>cmdarg()</tt> ===
cmdarg() is a mechanism to pass arguments to a nasal script (wrapped in properties) instead of "normal" function parameters. Note that cmdarg() should be primarily used in Nasal code embedded in XML files and should be considered depreciated otherwise (see [http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg18361.html] and [http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg18361.html]).
 
cmdarg() will keep working in (joystick) XML-'''bindings''' and on the top-level of embedded Nasal scripts (i.e. dialog and animation XML files).
 
As such, the cmdarg() function is primarily used for listener callbacks declared in XML markup, cmdarg() returns the listened-to property as props.Node object, so you can use it with all its methods (see [[$FG_ROOT]]/Nasal/props.nas) for example:
 
  print(cmdarg().getPath(), " has been changed to ", cmdarg().getValue())
 
The cmdarg() function avoids that you have to type the exact same path twice (once here and once in the setlistener() command) and it makes clear that this is the listened to property. Also, you can use all the nice props.Node methods on cmdarg() directly:
 
setlistener("/gear/launchbar/state", func {
      if (cmdarg().getValue() == "Engaged")
          setprop("/sim/messages/copilot", "Engaged!");
  }, 1, 0);
 
Use of cmdarg() outside of XML-bindings won't cause an error, but (still) return the last cmdarg() property. This just won't be the listened-to property anymore, but whatever the last legitimate cmdarg() user set. Most of the time it will be the property root of a joystick binding.
 
Don't make any assumptions and use cmdarg() only in one of these cases:
 
* binding: returns root of this binding's property branch. Needed for accessing an axis' value:        cmdarg().getNode("setting").getValue()
 
* dialog xml files: returns root of that file's property branch in memory. This can be used to let embedded Nasal change the dialog (e.g. clear and build lists) before the final layout is decided
 
* animation xml files: returns root of this model's place in /ai/models/ when used as AI/MP model. Examples:      /ai/models/multiplayer[3], /ai/models/tanker[1], etc.<ref>{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg14164.html |title=multiplayer generic properties |date=8 December 2007 |author=Melchior Franz}}</ref>
 
* AI aircraft XML files
 
* remotely invoking Nasal code by setting properties using the built-in telnet daemon (RPC)<ref>{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg00150.html |title=Calling FG functions via network interface |author=Melchior Franz |date=2 January 2006}}</ref><ref>{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg00336.html |title=Calling FG functions via network interface |author=Melchior Franz |date=7 January 2006}}</ref>.
 
'''In all cases, the cmdarg() call must not be delayed until later using settimer() or setlistener(). Because later it'll again return some unrelated property!
'''
 
=== <tt>fgcommand()</tt> ===
 
Runs an internal "fgcommand", see [[Bindings]] for a full list of available items (since they are most often used in bindings for input devices). The first argument is the name of the command (e.g. "property-interpolate") and the second is a props.Node object or its ghost which is the "argument" to the fgcommand. (This ensures that fgcommands are universal since props.Node/SGPropertyNode objects can really be accessed from anywhere in FlightGear.) Each fgcommand returns 1 if it succeeded or 0 if it failed.
 
The profiling related fgcommands '''profiler-start''' and '''profiler-stop''' are documented at [[Built-in Profiler]].
 
=== <tt>addcommand()</tt> ===
 
Add a fgcommand which can be used like a regular fgcommand (global scope, etc.). First argument is the name, second is the function to run. Note that this fgcommand will always return true! Inside the callback/function one can use either cmdarg() to retrieve the arguments or use the first argument passed to the function (since they will be the same).
 
=== <tt>removecommand()</tt> ===
 
As you can guess, there's also a removecommand() function which will remove '''any''' command – even those implemented in C++! As such it can be very dangerous and remove core functionality, so use with caution.
 
=== <tt>logprint()</tt> ===
 
The first argument should be a number specifying the priority (i.e. matching a sgDebugPriority: 0 for warn, etc.) and the rest of the arguments get concatenated to form the message for sglog.log() (basically the builtin logging mechanism). Returns the length of the message in bytes.
 
=== <tt>print()</tt> ===
Concatenates an arbitrary number of arguments to one string, appends a new-line, and prints it to the terminal. Returns the number of printed characters.
 
print("Just", " a ", "test");
 
 
 
=== <tt>getprop()</tt> ===
Returns the node value for a given path, or <tt>nil</tt> if the node doesn't exist or hasn't been initialized yet.
 
getprop(<path>);
 
Example:
 
print("The frame rate is ", getprop("/sim/frame-rate"), " FPS");
 
 
 
=== <tt>setprop()</tt> ===
Sets a property value for a given node path string. Always returns nil.
 
setprop(<path> [, <path>, [...]], <value>);
 
All arguments but the last are concatenated to a path string, with a slash (/) inserted between each element. The last value is written to the respective node. If the node isn't writable, then an error message is printed to the console.
 
Note: <tt>setprop()</tt> concatenates a list of input arguments by means of inserting a "/" in between. That is nice for properties, as this slash is part of the tree. However, when one wants to make use of indices, like [0], one has to concatenate by hand (using "~") ''inside'' one part of the string argument list. An example is:
 
  var i = 4;
  setprop("instrumentation","cdu","page["~i~"]","title","MENU");
 
This results in instrumentation/cdu/page[4]/title = 'MENU' (string)
 
Examples:
 
setprop("/sim/current-view/view-number", 2);
setprop("/controls", "engines/engine[0]", "reverser", 1);
 
 
'''Erasing a property from the property tree''': a property that has been created, for example through <tt>setprop()</tt> can be erased via
 
  props.globals.getNode("foo/bar").remove(); # take out the complete node
  props.globals.getNode("/foo").removeChild("bar"); # take out a certain child node
 
=== <tt>interpolate()</tt> ===
Give the value from a value or a source node to a destination node in given time.
 
  interpolate(<path>, <value>, <time>);
 
Examples:
  interpolate("controls/switches/nav-lights-pos", 1, 0.25); # After 25ms, nav-lights-pos = 1
  interpolate("controls/gear/brake-left-pos", getprop("controls/gear/brake-left"), 1); # After 1s, brake-left-pos = brake-left
 
=== <tt>settimer()</tt> ===
Runs a function after a given simulation time (default) or real time in seconds.
 
settimer(<function>, <time> [, <realtime=0>]);
 
The first object is a function object (ie, "func { ... }").  Note that this is different from a function call (ie, "func ( ... )"). If you don't understand what this means, just remember to always enclose the first argument in any call to settimer with the word "func" and braces "{ }", and it will always work. For instance, if you want print the words "My result" in five seconds, use this code:
 
settimer ( func { print ( "My result"); }, 5);
 
Inside the braces of the func object you can put any valid Nasal code, including a function call.  In fact, if you want to call a function with certain values as arguments, the way to do it is to turn it into a function object by enclosing it with a func{}, for example:
 
myarg1="My favorite string";
myarg2=432;
settimer ( func { myfunction ( myarg1, myarg2); }, 25);
 
The third argument is optional and defaults to 0, which lets the time argument be interpreted as "seconds simulation time". In this case the timer doesn't run when FlightGear is paused. For user interaction purposes (measuring key press time, displaying popups, etc.) one usually prefers real time.
 
# simulation time example
var copilot_annoyed = func { setprop("/sim/messages/copilot", "Stop it! Immediately!") }
settimer(copilot_annoyed, 10);
 
# real time example
var popdown = func ( tipArg ) {
  fgcommand("dialog-close", tipArg);
}
var selfStatusPopupTip = func (label, delay = 10, override = nil) {
    var tmpl = props.Node.new({
            name : "PopTipSelf", modal : 0, layout : "hbox",
            y: 140,
            text : { label : label, padding : 6 }
    });
    if (override != nil) tmpl.setValues(override);
   
    popdown(tipArgSelf);
    fgcommand("dialog-new", tmpl);
    fgcommand("dialog-show", tipArgSelf);
    currTimerSelf += 1;
    var thisTimerSelf = currTimerSelf;
    # Final argument 1 is a flag to use "real" time, not simulated time
    settimer(func { if(currTimerSelf == thisTimerSelf) { popdown(tipArgSelf) } }, delay, 1);
}
 
[[Nasal scripting language#settimer loops|More information about best practices for using the settimer function to create loops in Nasal is elsewhere on this page.]]
 
=== <tt>maketimer()</tt> (v. 2.11+) ===
 
<syntaxhighlight lang="php">
var timer = maketimer(<interval>, <function>)
var timer = maketimer(<interval>, <self>, <function>)
</syntaxhighlight>
 
<syntaxhighlight lang="php">
timer.start()
timer.stop()
timer.restart(<interval>)
timer.singleShot [read/write]
timer.isRunning [read-only]
</syntaxhighlight>
 
<syntaxhighlight lang="php">
# create timer with 1 second interval
var timer = maketimer(1.0, func { print('timer called'); });
# start the timer (with 1 second inverval)
timer.start();
# restart timer with 4 second interval
timer.restart(4);
 
# fire one single time in 6 seconds
timer.singleShot = 1;
timer.restart(6);
</syntaxhighlight>
 
<syntaxhighlight lang="php">
var Tooltip = {
  new: func
  {
    var m = {
      parents: [Tooltip]
    }
    m._hideTimer = maketimer(1.0, m, Tooltip._hideTimeout);
    m._hideTimer.singleShot = 1;
 
    return m;
  },
  run: func
  {
    if( !me._hideTimer.isRunning )
      me._hideTimer.start();
  }
  _hideTimeout: func
  {
    print('_hideTimeout');
  }
};
</syntaxhighlight>
 
=== <tt>systime()</tt> ===
Returns epoch time (time since 1972/01/01 00:00) in seconds as a floating point number with high resolution. This is useful for benchmarking purposes.
 
  #benchmarking example:
  var start = systime();
  how_fast_am_I(123);
  var end = systime();
  print("took ", end - start, " seconds");
 
=== <tt>carttogeod()</tt> ===
Converts cartesian coordinates x/y/z to geodetic coordinates lat/lon/alt, which are returned as a vector. Units are degree and meter.
 
var geod = carttogeod(-2737504, -4264101, 3862172);
print("lat=", geod[0], " lon=", geod[1], " alt=", geod[2]);
# outputs
lat=37.49999782141546 lon=-122.6999914632327 alt=998.6042055172776
 
 
 
=== <tt>geodtocart()</tt> ===
Converts geodetic coordinates lat/lon/alt to cartesian coordinates x/y/z. Units are degree and meter.
 
var cart = geodtocart(37.5, -122.7, 1000); # lat/lon/alt(m)
print("x=", cart[0], " y=", cart[1], " z=", cart[2]);
# outputs
x=-2737504.667684828 y=-4264101.900993474 z=3862172.834656495
 
 
 
=== <tt>geodinfo()</tt> ===
Returns information about geodetic coordinates. Takes two arguments: lat, lon (in degree) and returns a vector with two entries, or nil if no information could be obtained because the terrain tile wasn't loaded. The first entry is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material, or nil if there was no material information available, because there is, for instance, an untextured building at that spot or the scenery tile is not loaded.
 
var lat = getprop("/position/latitude-deg");
var lon = getprop("/position/longitude-deg");
var info = geodinfo(lat, lon);
if (info != nil) {
    print("the terrain under the aircraft is at elevation ", info[0], " m");
    if (info[1] != nil)
        print("and it is ", info[1].solid ? "solid ground" : "covered by water");
}
 
A full data set looks like this:
 
debug.dump(geodinfo(lat, lon));
# outputs
[ 106.9892101062052, { light_coverage : 0, bumpiness : 0.5999999999999999, load_resistance : 1e+30,
solid : 0,  names : [ "Lake", "Pond", "Reservoir", "Stream", "Canal" ], friction_factor : 1,
rolling_friction : 1.5 } ]
 
Note that geodinfo is a *very* CPU intensive operation, particularly in FG 2.4.0 and earlier, so use sparingly ([http://flightgear.org/forums/viewtopic.php?f=4&p=135044#p135044 forum discussion here]).
 
=== <tt>parsexml()</tt> ===
 
This function is an interface to the built-in [http://expat.sourceforge.net/ Expat XML parser]. It takes up to five arguments. The first is a mandatory absolute path to an XML file, the remaining four are optional callback functions, each of which can be nil (which is also the default value).
 
var ret = parsexml(<path> [, <start-elem> [, <end-elem> [, <data> [, <pi> ]]]]);
<start-elem>  ... called for every starting tag with two arguments: the tag name, and an attribute hash
<end-elem>    ... called for every ending tag with one argument: the tag name
<data>        ... called for every piece of data with one argument: the data string
<pi>          ... called for every "processing information" with two args: target and data string
<ret>        ... the return value is nil on error, and the <path> otherwise
 
Example:
 
var start = func(name, attr) {
    print("starting tag ", name);
    foreach (var a; keys(attr))
        print("\twith attribute ", a, "=", attr[a]);
}
var end = func(name) { print("ending tag ", name) }
var data = func(data) { print("data=", data) }
var pi = func(target, data) { print("processing instruction: target=", target, " data=", data) }
parsexml("/tmp/foo.xml", start, end, data, pi);
 
=== airportinfo() ===
Function for retrieval of airport/runway information.
Usage:
 
  var apt = airportinfo("KHAF");  # get info about KHAF
  var apt = airportinfo(lat, lon); # get info about apt closest to lat/lon
  var apt = airportinfo();        # get info about apt closest to aircraft 
 
The command debug.dump(airportinfo("KHAF")) outputs this:
 
  { lon : -122.4962626410256, lat : 37.51343502564102, has_metar : 0,
  runways : { 12 : { stopway2 : 0, threshold1 : 232.5624,
  lon : -122.5010889999999, lat : 37.513831, stopway1 : 0, width : 45.72,
  threshold2 : 232.5624, heading : 138.1199999999999, length : 1523.0856 } },
  elevation : 20.42159999999999, id : "KHAF", name : "Half Moon Bay" }
 
That is: a hash with elements lat/lon/elev/id/name/has_metar for the airport, and a hash with runways, each of which consists of lat/lon/length/width/heading/threshold[12]/stopway[12]. Only one side of each runway is listed -- the other can easily be deduced.
 
=== Positioned Object Queries ===
 
Several functions exist to query the navigation database of 'positioned' objects, i.e items with a defined location in the simulation world. (These objects all inherit from <tt>FGPositioned</tt> internally). The functions return either one, or a vector, of wrapped Nasal objects. These are efficient, but unlike a hash they cannot simply be <tt>debug.dump()</tt> to view all their members and methods.
 
When the query functions take a position, the default value is the current aircraft location. An alternative location can be supplied by passing two number (lat, lon), a Geo.Coord object, or any positioned object or waypoint retrieved from another query.
 
;findAirportsWithinRange()
: Find all airports within a specified range (in NM) of the current position or explicit position
;findAirportsByICAO()
: Find airports matching a complete or partial ICAO code. In particular this can search for all airports starting with a two or three letter prefix.
;navinfo()
: Return a list of navaids near a location by type and ident. Type should be 'fix', 'vor', 'ndb', 'dme'
;findNavaidsWithinRange()
: Search for navaids within a particular range (in NM), optionally limited by type. This provides a 'find the closest ILS' function
;findNavaidByFrequency()
: find the closest navaid (of a particular type) matching an exact frequency
;findNavaidsByFrequency()
: find all the navaids matching a particular frequency and optional type, sorted by distance from the search location.
 
All positioned objects returned by the above methods have the following members:
;id
: Identifier - ICAO code for airports, published ident for navaids and fixes
;lon
: degrees longitude
;lat
: degrees latitude
 
Depending on type, the following members are also available:
;name
: the full name of the airport or navaid if one is defined
;elevation
: the ASL elevation of the object in feet
 
For navaids, the following members are available:
;frequency
: the navaid frequency in kHz
;type
: the navaid type as a string: vor, dme, loc, ils, ndb
;course
: the degrees course associated with the navaid, for localiser and ILS stations
 
=== <tt>flightplan()</tt> ===
 
Function to retrieve the active flight-plan object, or load a flight plan from a file path.
 
Usage:
var fp = flightplan();
var fp = flightplan('/some/path/to/a/flightplan.xml');
 
In advance of converting the Map and NavDisplay to use the [[Canvas]], James has improved the "flightplan()" extension function of the [[Nasal]] scripting interpreter to expose the full route-path vector for each flight plan leg, as a vector on the leg .
 
 
<syntaxhighlight lang="php">
var fp = flightplan();
for (var i=0; i<fp.getPlanSize(); i += 1)
{
  var leg = fp.getWP(i);
  debug.dump(leg.path());
}
</syntaxhighlight>
 
 
<references/>

Latest revision as of 17:21, 11 February 2015