This document is a tutorial on how to interface Nasal scripts with FlightGear. It is not an introduction to the Nasal language itself. For that, see Andy's Nasal website at http://www.plausible.org/nasal. The information there is sparse, but you should have it ready for reference while reading this document.
<binding>
tag.
The relevant command type is "nasal", and you place your Nasal code
inside of the <script>
tag:
<binding> <command>nasal</command> <script> print("Binding Invoked!"); </script> </binding>
The code above invokes the print()
function. This is
a simple extension function that simply prints out its arguments, in
order, to the FlightGear console as a single-line log entry. It is
useful for debugging, but little else.
Some command have SGPropertyNode arguments. This argument is
available as a props.Node
object and is returned from the
built-in cmdarg()
function. See below for full
documentation, but as an example the following "joystick axis" binding
will print the current axis value to the console:
<binding> <command>nasal</command> <script>print(cmdarg().getNode("value").getValue());</script> </binding>
Note that the current implementation parses the Nasal code inside
the <script>
tag each time it is run. This means
that you should avoid placing large code blocks inside a
<script>
tag for performance reasons. It also
means that any local variables you set inside the script will be lost
the next time it is run. See the discussion about namespaces and
Nasal source files below for more information.
You can use getprop()
and setprop()
functions to interact with the global property tree. They work as you
would expect. For example, the following Nasal code copies the value
of the "/sim/foo" property to the "/sim/bar" property:
setprop("/sim/bar", getprop("/sim/foo"));
Note that Nasal's notion of "type" is coarser than that of the
SGPropertyNode class. Numers and strings come out of
getprop()
as Nasal scalars, of course, and boolean
properties are converted to a numeric 1 or 0 by
getprop()
. But all non-string values passed to
setprop()
become doubles in the property tree. The
props.Node interface (see below) provides the ability to set explicit
types in the property tree.
A useful feature of getprop and setprop that you should be aware of is that they both accept variable numbers of arguments. These are concatenated together to form a property tree path internally within the function. This avoids the need for extensive string concatenation in the script for the common case where you are acting on a variable property root. That is, you can do:
ThisAircraft = "/sim/my/aircraft/properties"; setprop(ThisAircraft, "crashed", 0);to set the property "/sim/my/aircraft/properties/crashed" to false. This feature is useful for writing Nasal functions that will work on parameterized property trees.
Think of the getprop()
and setprop()
functions as equivalents of the fgGet*()
and
fgSet*()
C++ functions. They provide simple and easy
access to the global property tree.
For some situations, however, you need finer control over the
property nodes. For these situations you can use the props module,
which provides a Node
class similar to the SimGear
SGPropertyNode
class. The global tree is available as a
Node
object named props.globals
. You can
use the getNode()
method on it to retrieve an arbitrary
sub-node. The children of any given node can be inspected with
getChild()
and getChildren()
methods. And
of course its name, index, and value can be accessed with appropriate
methods. See the reference below for complete documentation.
As seen above in the discussion on fgcommand()
, you
can also create new, "rootless" Node
objects using
props.Node.new()
.
The most powerful method on a Node object is
setValues()
. This takes a Nasal hash table as its only
argument, and initializes properties under the node using the
key/value pairs in the hash. This works with vectors (i.e. indexed
properties) and recursively, essentially making a deep copy of a Nasal
object in the property tree.
For debugging (and amusement) purposes, the props
modules also defines a dump()
function which recursively
prints the state of a property node to the console. Try
props.dump(props.globals)
to see it walk the entire tree.
Just as Nasal code can be run as a command binding from the
property tree, existing FlightGear commands can be invoked by Nasal
code using the fgcommand()
function. The first argument
to this function is a string, equivalent to what you would place
inside the <command>
tag in a property binding.
The second argument specifies the property tree that will be passed as
the "arguments" to the command. It can be a either a string
specifying a path in the global property tree or a props.Node object.
Example:
# Use a temporary property in the global tree ShowDialog = func { setprop("/nasal/tmp/dialog-args/dialog-name", arg[0]; fgcommand("dialog-show", "/nasal/tmp/dialog-args"); } # Does the same thing, but with a rootless Node object ShowDialog = func { fgcommand("dialog-show", props.Node.new({dialog-name : arg[0]}); }These both define a
ShowDialog()
function, which pops up
a named dialog from the ShowDialog("autopilot")
will therefore pop up the
autopilot dialog just as if it had been bound to a key.
The first variant uses a "temporary" property tree in the global
property space to hold the arguments. The second creates a new
props.Node
object instead. Note that
props.Node.new()
accepts a hash table as its argument and
uses that to initialize the returned property tree.
Nasal is a "real" language, with real namespaces and modules. What
"really" happens when you run a script binding is that the script is
treated as a function body and bound (lexically, in the functional
programming sense) to a single global namespace. It is as if it were
enclosed in a func { ... }
expression and executed inside
a "file" containing all the global symbols.
Some symbols in the global namespace are built-in extension
functions, like the print/getprop/setprop we have already seen.
Others are objects (or hash tables -- they are the same thing in
Nasal). These objects act as namespaces and can contain code of their
own. One such example is the math library. The built-in math
functions live in their own namespace and are accessible as
math.sin()
, math.cos()
, etc...
The global namespace itself is available as a module named "globals". This allows you to create new symbols in the global namespace if you desire (be careful!) and to otherwise inspect its contents. It's just a hash table, after all. The following code will print all the symbols found in the global namespace:
print("These are the symbols found in the global namespace:"); foreach(i; keys(globals)) { print(" ", i); }
You can write your own modules, too. The mechanism is very simple:
merely create a file with a ".nas" extension in the Nasal directory of
your FlightGear base package. FlightGear will read, parse and execute
these files during initialization, and create a module of the same
name for use by your scripts. So you can write, say, a "mouse.nas"
script. Functions defined therein are available to your script
bindings (and any other nasal code on the system) as members of the
global "mouse" object. So you can define bindings that do things like
mouse.handleXAxis(offset)
to call functions defined in
the mouse.nas file (remember that "offset" is an automatically
initialized variable containing the binding's offset argument).
Nasal modules can also be imported from the property tree at initialization. This is useful for applications like aircraft-specific scripts that need to be loaded only when that aircraft is active. The usage is simple: the Nasal interpreter creates a module for every property node child of "/nasal" that it finds at initialization time. Example:
<nasal> <c172> <file>Aircraft/c172/c172.nas</file> </c172> </nasal>This creates a module named "c172" and loads the contents of the Aircraft/c172/c172.nas file into it. The module name is, by default, the same as the property node. But this can be overridden with the
<module>
tag. This trick can be useful if you need
to load extra script source into a previously-initialized module.
You can also write literal Nasal scripts inside the property files
by including it in a <script>
tag. This sample
uses the <module>
tag to add an extra function to
the math library.
<nasal> <c172-tmp1> <!-- Use a unique, dummy name --> <module>math</module> <script><[CDATA[ # The math library doesn't include this, because Andy is a pedant # and thinks it's dangerous. But the c172 code just *has* to have # it. atan = func { return atan2(arg[0], 1) } ]]></script> </c172-tmp1> </nasal>Note the use of a CDATA declaration. This is required to properly escape XML special characters like "
<
". As it
happens, this code doesn't use them. But the CDATA is good practice
nonetheless.
These are the built-in extension functions available to all Nasal code in FlightGear. Be sure to examine the core Nasal documentation at the Nasal site as well. Only FlightGear-specific library code is documented here:
func { ... }
expression. The second argument is a (floating point) number
specifying a delta time in seconds. Some time after that delta
time has elapsed, the specified function will be invoked. Exact
timing will depend on the frame rate of the simulator.
props.Node
object. The remaining arguments specify
pairs of value/delta-time numbers. The property is interpolated
smoothly from its current value to the new value over the
specified time delta, in seconds. Multiple value pairs can be
used to indicate successive values or to acheive a piecewise
linear approximation to a non-linear function. This function
cancels any preexisting interpolation for that property, so
interpolate("/sim/countdown", 0, 0)
has the
effect of cancelling interpolation of "/sim/countdown" and setting
its value to zero.
props.Node
class wraps a SGPropertyNode object,
either in or outside of the global property tree. It supports the
following methods:
Node
object. Takes a hash argument to initialize the
new node via setValues().
Node
object representing the root of the
global property tree; the Nasal equivalent of
globals->get_props()
Node
object and all of its children to the console.
Very useful for debugging and exploration.
The FGNasalSys object has a parseAndRun()
method to
which you can pass arbitrary Nasal source code for immediate
execution:
auto n = globals->get_subsystem(); if(! n->parseAndRun("print('This script was called from C++!')")) SG_LOG(SG_GENERAL, SG_ALERT, "My Nasal code failed :(");
You can also use parseScript()
to get a pointer to a
FGNasalScript
object. This object supports a
call()
method which you can use to invoke the script
later on, at a time of your choosing. If you will be invoking the
script multiple times, this mechanism can be more efficient because it
avoids the parsing and code generation overhead for the successive
calls.
auto n = globals->get_subsystem(); FGNasalScript* script = n->parseScript("print('Spam!')")) if(!script) SG_LOG(SG_GENERAL, SG_ALERT, "My Nasal code failed :("); ... for(int i=0; i<1000; i++) script->call(); // Spam the console
Note that there is no way to inspect the return value of the function that you called. It simply returns a boolean indicating successful execution. Handling of "native" Nasal data structures has to be done via the Nasal extension API. See below.
You have three options for invoking C++ code from Nasal. The first two take advantage of pre-existing FlightGear mechanisms for registering "callback" handlers for specific events.
If your task is sufficiently general, you should consider defining it as a new FGCommand using the existing interface. This can be invoked efficiently from both Nasal code (using the fgcommand() function) and existing property bindings, and is very easy to do. Simply define a handler function which takes a property tree as an argument and returns a bool (to indicate successful execution), and register it during initialization with the global command manager:
// Define your handler function: bool my_new_command(SGPropertyNode* arg) { ... } ... // And register it in your initialization code: globals->get_commands()->addCommand("my-new-command", my_new_command); ...
This mechanism works well when your C++ code is a "global" function that you will want to call from many locations with potentially differing data. For some applications, however, you want to register a handler that will be called only by code involved with computations on a single data set.
For this, there is the property listener interface. You can create a subclass of SGPropertyChangeListener which implements the valueChange, childAdded and/or childRemoved methods and associate it with a specific property node in the global tree. You can then "invoke" this handler from Nasal code (or from anywhere else) by simply setting the property value.
I haven't tested the property listener interface, and it requires somewhat more typing to implement; so I will include no example here. It is also rather rarely used by existing FlightGear code (the property picker GUI is the only significant application I could find).
This is the third mechanism for invoking C++ (strictly C, in this
case) code from Nasal. This is the API you must use if you want to
inspect and/or modify Nasal data structures, or create a function
object that will be visible to Nasal scripts as a callable function.
Unfortunately, there really isn't space here to document this API
fully. For now, examin the nasal.h
header which defines
it, and the lib.c
and mathlib.c
source files
which implement the existing built-in library functions. The
FlightGear-specific extension functions in NasalSys.cxx
and nasal-props.cxx
are also good examples.
But for most purposes, consider the first two mechanisms instead. FlightGear's general inter-module communication mechanism is the property tree, which is exposed from both Nasal and C++ code already. A Nasal extension function, by definition, is useful only to Nasal code. Even worse, data structures definied by a Nasal interface are completely invisible to the C++ world.