Creating new Nasal scripts

From FlightGear wiki
Jump to navigation Jump to search


Editing code files

Note that there is currently no way to tell FlightGear to reload Nasal scripts from the global Nasal directory at runtime, so in order to see changes take effect, you will have to exit and restart FlightGear for the time being. Note that there are some workarounds available, see: reloading Nasal code without re-starting FlightGear.

Also, note that as of 05/2009, Nasal in FlightGear does not yet support any form of dependency resolution. In other words, there's no "import", "require" or "include" directive - this is also why most code in FlightGear is wrapped inside a _setlistener() call instead, which in turn waits for a FlightGear signal before executing the code (see below for details).


Creating new Scripts

Nasal scripts need to be plain text files, saved with a *.nas extension. They can be created and written using a conventional plain text editor like notepad, gedit, kate etc. But you can also use a programming text editor with syntax lighting support, see Howto:Syntax highlighting for Nasal.

We are also currently planning to add some very basic syntax highlighting support to the built-in Nasal Console using the new Canvas system.

Aircraft specific Nasal code

Generally, aircraft specific Nasal scripts reside in the corresponding aircraft's folder (or a corresponding /Nasal subfolder) where they are usually included by adding a corresponding <nasal> tag to the aircraft-set.xml file (see Writing simple scripts in "nasal"). Also see the section on namespaces which contains more specific examples.

Instrument specific Nasal code

While instrument specific scripts are saved within the instrument's folder (as previously mentioned, Nasal scripts can also be embedded in various other XML files), Nasal scripts driving shared instruments are generally stored in $FG ROOT/Aircraft/Generic/.

Nasal code as bindings in XML files

Nasal scripts can also be used as "binding" 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.

System-wide Nasal code

Nasal scripts that are not specific to certain aircraft, instruments or other uses, generally reside in the system-wide $FG ROOT/Nasal directory. Scripts that are placed inside this directory (with a *.nas extension) are automatically loaded and run during FlightGear startup.

Nasal sub modules

As of 06/2011, FlightGear also supports so called Nasal "sub modules" which may reside in their own sub folder under $FG_ROOT/Nasal/ and which provide support for on-demand loading at runtime by toggling properties.

Some advantages are:

  • Nasal files can be grouped neatly instead of all scripts being mixed up in a single fgdata/Nasal directory. Grouping makes a lot of sense for modules consisting of several scripts - local weather is the best example.
  • Guaranteed loading sequence. Submodules are loaded _after_ the main fgdata/Nasal scripts, so they can rely on all fgdata/Nasal content to be already present. No more need for awkward listener callbacks, just to make sure that basic "props" or "gui" modules are available.
  • Finally, users have the option to disable loading modules. Unfortunately, just loading scripts (code/data) into memory already causes certain _run-time_ performance effects - even if the Nasal code was never executed (so even when all listeners/timers were disabled).

Please note that there is a difference between the _individual_ Nasal files in fgdata/Nasal and files belonging to a common Nasal _module in general (no matter whether loaded at run-time or loaded at start-up using a "<nasal>" tag).

The individual Nasal files in fgdata/Nasal have an own namespace _each_. The namespace get's the name of the Nasal file itself. So if you have a "gui.nas" in the directory, then you can reference a symbol "foo" using "gui.foo".

Nasal modules also have a single namespace. But all files belonging to the module share this _single_ namespace. The name of their namespace is made from its directory (for the run-time loadable modules), or from the specific tag given below the <nasal> XML element, which are often used for a/c specific modules (e.g. <nasal><ufo>...</ufo></nasal> creates the ufo Nasal namespace in ufo-set.xml).

So each Nasal file in a new Nasal "module" folder now shares the same namespace.

Another important thing to keep in mind is that Nasal sub modules should preferably register a listener to handle initialization, because they are not necessarily loaded by default but rather "on demand". The signal property to listen to will be in /nasal/MODULE_NAME/loaded - for example:

var init = func {
 # this is where you put all your startup code
 # to ensure that it will only be called
 # AFTER all sub modules have been processed
}
_setlistener("/nasal/my_module/loaded", init);

This is why you need to wrap everything in a function and register a listener, so that the code is only invoked AFTER ALL sub module files have been LOADED - otherwise, you'll have unresolved references.

As you can see in $FG_ROOT/Nasal/local_weather/compat_layer.nas there's a sub module listener registered that will be fired once ALL source files in the sub module folder have been processed - this is basically the signal to fire off the callback, so that the code can actually start running. The local weather system doesn't use a separate function, but rather an anonymous func: https://sourceforge.net/p/flightgear/fgdata/ci/next/tree/Nasal/local_weather/compat_layer.nas#l76

The whole point of this technique is being able to tell when all files have been parsed by Nasal - so that you know that you can actually start running the code, and rely on all symbols being available in the module's namespace.

All sub modules are automatically loaded AFTER $FG_ROOT/Nasal has been processed - so your sub modules only need to register their own "loaded" listener to make sure that the sub module code is only actually run once all files have been processed. Nasal core modules (such as gui.nas io.nas etc) are automatically available to every sub module.


For more information on Nasal sub modules, please see:

User specific Nasal scripts

It's also possible to put Nasal files into $FG_HOME/Nasal/, that is: ~/.fgfs/Nasal/ on Unix, and %APPDATA%\flightgear.org\Nasal\ on MS Windows. This has the following advantages:

  • one doesn't have to mix local extensions with standard files
  • one is less likely to lose such local additions when upgrading
  • one doesn't need write permission to $FG_ROOT/Nasal/ or
  • one doesn't have to become "root" to edit such files

The files are guaranteed to be read after all the files in $FG_ROOT/Nasal/, so one can safely use elements of files like props.nas (props.Node), or globals.nas (setlistener() without leading underscore).

The files are guaranteed to be read in alphabetic order. So, if there are two files where one depends on the other, just name them appropriately.

The contents of each file are added to a namespace derived from the filename. So, all functions and variables of a file ~/.fgfs/Nasal/local.nas will be added to nasal namespace "local", and a function test() is globally accessible as local.test().

It's possible to extend a standard module like "math" with definitions in ~/.fgfs/Nasal/math.nas, though this should, of course, not be exploited by code that is to be submitted to cvs.