List of Nasal extension functions: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
Line 72: Line 72:
=== <tt>getprop()</tt> ===
=== <tt>getprop()</tt> ===


As of FlightGear 3.1, getprop()/setprop() arguments (those that form a path) can now be numeric to specify a index, so:
Returns the node value for a given path, or <tt>nil</tt> if the node doesn't exist or hasn't been initialized to a value yet.  
 
<syntaxhighlight lang="nasal">
getprop(<path> [, <path>, [...]]);
</syntaxhighlight>
 
Several arguments will be added together to produce a path, with numeric arguments specifying indexes (as of FlightGear 3.1), so
<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
getprop("canvas/by-index", "texture", 1, "name");
getprop("canvas/by-index", "texture", 1, "name");
</syntaxhighlight>
</syntaxhighlight>
: is now the same as:
is the same as:
<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
getprop("canvas/by-index/texture[1]/name");
getprop("canvas/by-index/texture[1]/name");
</syntaxhighlight>
: (see [https://gitorious.org/fg/flightgear/commit/5eee5e42ae4f5cf56283b3bf5a3be46efc2b51c4 merge request 54] and [https://gitorious.org/fg/flightgear/commit/34ed79e5f88ffdfc5e651a1fe3e639cb8f4d3353 actual commit])
Returns the node value for a given path, or <tt>nil</tt> if the node doesn't exist or hasn't been initialized yet.
<syntaxhighlight lang="nasal">
getprop(<path>);
</syntaxhighlight>
</syntaxhighlight>


Line 92: Line 91:
<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
print("The frame rate is ", getprop("/sim/frame-rate"), " FPS");
print("The frame rate is ", getprop("/sim/frame-rate"), " FPS");
for (var i=0; i < 10; i += 1) {
    print("View ", i, "'s name is: ", getprop("/sim/view", i, "name"));
}
</syntaxhighlight>
</syntaxhighlight>



Revision as of 14:45, 19 February 2014

This article is incomplete. Please help improve the article, or discuss the issue on the talk page.


Nasal Core Library

For a list of functions implemented in C, that are available to Nasal implementations in general, see [1]. Note that only the thread, io, math, and utf8 libraries are currently included in SimGear.

FlightGear's Extension Functions

cmdarg()

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 [2]).

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.[1]
  • AI aircraft XML files
  • remotely invoking Nasal code by setting properties using the built-in telnet daemon (RPC)[2][3].

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!

fgcommand()

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.

addcommand()

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

removecommand()

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.

print()

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

logprint()

Similar, but the first argument is reserved for number specifying the priority (i.e. matching a sgDebugPriority object: 1 for bulk, 2 for debug, 3 for info, and 4 for warn). Also see printlog() in globals.nas: it does essentially the same but with named levels ("bulk", etc.). (The latter relies on print(), however, and does not make use of the sophistication of sglog in dealing with source file and line number.)

getprop()

Returns the node value for a given path, or nil if the node doesn't exist or hasn't been initialized to a value yet.

getprop(<path> [, <path>, [...]]);

Several arguments will be added together to produce a path, with numeric arguments specifying indexes (as of FlightGear 3.1), so

getprop("canvas/by-index", "texture", 1, "name");

is the same as:

getprop("canvas/by-index/texture[1]/name");

Example:

print("The frame rate is ", getprop("/sim/frame-rate"), " FPS");
for (var i=0; i < 10; i += 1) {
    print("View ", i, "'s name is: ", getprop("/sim/view", i, "name"));
}

setprop()

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: setprop() 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 setprop() 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

interpolate()

Give the value from a value or a source node to a destination node in given time.

interpolate(<path>, <value>, <time>);

Examples:
<syntaxhighlight lang="nasal">
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

settimer()

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

More information about using the settimer function to create loops.

maketimer() (v. 2.11+)

As of 2.11, there is a new API for making a timer that allows more control over what happens in a timer – as opposed to setting one and forgetting about it.

var timer = maketimer(<interval>, <function>)
var timer = maketimer(<interval>, <self>, <function>)
timer.start()
timer.stop()
timer.restart(<interval>)
timer.singleShot [read/write]
timer.isRunning [read-only]
# 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);
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');
  }
};

geodinfo()

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 (forum discussion here).

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 FGPositioned internally). The functions return either one, or a vector, of wrapped Nasal objects. These are efficient, but unlike a hash they cannot simply be debug.dump() 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

flightplan()

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.


var fp = flightplan();
for (var i=0; i<fp.getPlanSize(); i += 1)
{
  var leg = fp.getWP(i);
  debug.dump(leg.path());
}

systime()

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

carttogeod()

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

geodtocart()

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

parsexml()

This function is an interface to the built-in 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);

resolvepath()

SimGear features its own path resolving framework that takes a relative path and returns an absolute path, checking from base directories such as $FG_ROOT, $FG_HOME, $FG_AIRCRAFT, and the current aircraft directory (/sim/aircraft-dir). This function in Nasal takes a path string and returns the absolute path or an empty string if the path couldn't be resolved.

Example:

var guess_path = func(path...) {
    var path_concat = string.join(path, "/");
    var file_path = resolvepath(path_concat);
    if (file_path == "") die("Path not found: "~path_concat);
    return file_path;
}

HTTP module (v. 2.99+)

Get remote data using the HTTP.

http.load()

Load resource identified by its URL into memory.

var request = http.load(<url>);
http.load("http://example.com/test.txt")
    .done(func(r) print("Got response: " ~ r.response));

http.save()

Save resource to a local file.

var request = http.save(<url>, <file_path>);
http.save("http://example.com/test.png", getprop('/sim/fg-home') ~ '/cache/test.png')
    .fail(func print("Download failed!"))
    .done(func(r) print("Finished request with status: " ~ r.status ~ " " ~ r.reason))
    .always(func print("Request complete (fail or success)"));

rand()

Return a random number as generated by sg_random.

srand()

Seed the random number generator based upon the current time. Returns 0.

abort()

Wrapper for the C++ library abort() function – i.e. it just aborts the process without regard to what's happening. For exiting (gracefully) from FlightGear use the fgcommand "exit" instead.

  1. Melchior Franz (8 December 2007). multiplayer generic properties.
  2. Melchior Franz (2 January 2006). Calling FG functions via network interface.
  3. Melchior Franz (7 January 2006). Calling FG functions via network interface.