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 @@ + + + +Using Nasal with FlightGear + + + + + +

Using Nasal with FlightGear

+ +

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

Basic Nasal/FlightGear Integration

+ +

Calling Nasal from Configuration File Bindings

+ +Nasal scripts can be used as FGBinding objects, and can therefore +appear anywhere in a configuration file (keyboard, mouse and joystick +bindings, etc...) that accepts a <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. + +

Setting and Inspecting FlightGear Properties

+ +

getprop() and setprop()

+ +

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

The props module

+ +

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

Invoking FlightGear Commands from Nasal

+ +

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 /gui/dialogs directory. Calling +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. + +

Writing extended Nasal code in source files

+ +

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

Including Nasal Code from Configuration Files

+ +

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

Function Reference

+ +

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: + +

Global Functions

+ +
+ +
rand() +
Returns a random number in the range [0:1) (that is, 0.0 is a + possible return value, but 1.0 is not). + +
getprop() +
The arguments are concatenated to form a path to a global + property node. Returns the value of that node, or nil if it does + not exist. + +
setprop() +
The final argument specifies a value to set. The remaining + arguments are concatenated to form a property path as in + getprop(). + +
print() +
The arguments are printed, in order, to the FlightGear console. + A newline is appended by the logging code, none is + required. + +
fgcommand() +
The first argument is a string specifying a FlightGear command to + execute (e.g. "show-dialog"). The second is a property sub-tree + (either a global path string or a props.Node object) which will be + passed to the command as arguments. + +
settimer() +
The first argument is a Nasal expression which evaluates to a + Nasal function object (it can be either a symbol name for a + function or a literal 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. + +
interpolate() +
The first argument specifies a property. It can be either a + string representing a global property name or a + 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. +
+ +

Property Module

+ +
+
Node +
The props.Node class wraps a SGPropertyNode object, + either in or outside of the global property tree. It supports the + following methods: +
+
getType() +
Returns the "type" of the SGPropertyNode object. The return value +is a string; one of: NONE, ALIAS, BOOL, INT, LONG, FLOAT, DOUBLE, +STRING or UNSPECIFIED. +
getName() +
Returns the name of the property node. +
getIndex() +
Returns the child index of the property node. +
getValue() +
Returns the current value of the node, or nil if it has none. +
setValue() +
Sets the current value as either a string or a double, depending +on the internal type of the argument. +
setIntValue() +
Sets the current value, forcing the type to INT +
setBoolValue() +
Sets the current value, forcing the type to BOOL +
setDoubleValue() +
Sets the current value, forcing the type to DOUBLE +
getParent() +
Returns a Node object representing this node's parent, or nil if +it has none. +
getChild() +
Returns a named child, or nil if it does not exist. If multiple +children with that name exist, returns the one with an index of zero. +
getChildren() +
Returns a vector containing all the node's children. +
removeChild() +
Removes a child by name (first argument) and index (second argument). +
getNode() +
Returns a Node specified by its "relative path" to this node, or +nil if none exists. The optional second argument, if true, causes the +node to be created if it does not exist. +
setValues() +
Takes a hash as argument, and sets all the key/value pairs in the +hash as property subnodes of the object. This works recursively, with +sub-hashes and vectors; thus making a deep copy of the Nasal hash in +the property tree. +
+ +
props.Node.new() +
Static "constructor" function returning a new, rootless + Node object. Takes a hash argument to initialize the + new node via setValues(). + +
props.globals +
This is a Node object representing the root of the + global property tree; the Nasal equivalent of + globals->get_props() + +
props.dump() +
This method prints out a "dump" of the state of a single + Node object and all of its children to the console. + Very useful for debugging and exploration. + +
+ +

Integrating C++ code and Nasal

+ +

Calling Nasal from C++

+ +

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

Calling C++ from Nasal

+ +

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

Extending Nasal

+ +

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