diff --git a/docs-mini/Nasal.html b/docs-mini/Nasal.html new file mode 100644 index 000000000..a7a21d0cc --- /dev/null +++ b/docs-mini/Nasal.html @@ -0,0 +1,488 @@ + + +
+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:
+
+
+FGNasalSys n = (FGNasalSys*)globals->get_subsystem("nasal"); +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.
+
+
+FGNasalSys n = (FGNasalSys*)globals->get_subsystem("nasal"); +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. + +