Modules.nas

From FlightGear wiki
Revision as of 08:52, 24 February 2020 by Jsb (Talk | contribs) (unload() function)

Jump to: navigation, search

Nasal runtime re-loadable modules

Introduction and motivation

Nasal (Flightgears integrated scripting language) comes with a limited number of core functions. The language was extended by C++ functions, as well as libraries written in Nasal (e.g. props, io, math, debug ...).

The latter are stored in the Fgdata repository under /Nasal and loaded automatically by FlightGear. Optional code, which is loaded only on demand, can be handled either by the legacy module system implemented in C++ or by modules.nas if run-time re-load support is required.

For more information on how and when these files are loaded see Nasal Initialization.

Differences between add-ons and modules

While there are many similarities between add-ons and modules, some words on the differences:

Modules are distributed with FlightGear as part of Fgdata. Add-ons have to be downloaded separately by a FlightGear user from wherever the author of the add-on publishes the add-on.

Modules can be loaded e.g. by an aircraft if the aircraft developer wants to make use of the module. Add-ons are selected by the user before launching FlightGear, thus they may or may not be available at runtime.


HowTo / Examples

First some examples, the API documentation follows below.

Nasal modules in an aircraft (e.g. for development)

Nasal code for an aircraft is usually loaded by a XML declaration in the <aircraft>-set.xml file like this:

<PropertyList>
    <nasal>
        <foo> <!-- Nasal namespace foo -->
            <file>Nasal/foo.nas</file>
        </foo>
        <bar> <!-- Nasal namespace bar -->
            <file>Aircraft/Generic/foo.nas</file>
        </bar>
        <myAircraft> <!-- Nasal namespace myAircraft-->
            <file>Nasal/efis_module.nas</file>
        </myAircraft>
    </nasal>
</PropertyList>

This is fine unless you are developing a Nasal subsystem for the aircraft and have to restart FlightGear every time you edit a few lines. For this case you could consider to use the Module class (defined in FGDATA/Nasal/modules.nas) and make your code re-loadable at runtime, which means you do not have to restart whole FlightGear just for a few lines of edited Nasal code.

Example Nasal/efis_module.nas:

#-- load EFIS as reloadable module
var my_efis = modules.Module.new("myAircraft_EFIS"); # Module name
my_efis.setDebug(1); # 0=(mostly) silent; 1=print setlistener and maketimer calls to console; 2=print also each listener hit, be very careful with this! 
my_efis.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal/EFIS");
my_efis.setMainFile("myAircraft-efis.nas");
my_efis.load();

Now add a menu item for easy reloading like this:

Note

The module name in the property path must match the name passed to modules.Module.new()

<menubar>
    <default>
        <menu n=100>
            <!-- normal aircraft menu -->
        </menu>
        <menu n=101>
            <label>Aircraft Development</label>
            <item>
                <label>Reload EFIS</label>
                <binding>
                    <command>property-assign</command>
                    <property>/nasal/modules/myAircraft_EFIS/reload</property>
                    <value>1</value>
                </binding>
            </item>
        </menu>
    </default>
</menubar>
Tip

You can also call myAircraft.my_efis.reload(); to trigger the reload. myAircraft is the namespace given in the <aircraft>-set.xml file, my_efis is the module object (see above).

Structure of Nasal reloadable modules

A module must contain at least the file main.nas (default, other filename is possible) and should contain the functions main() and unload().

main() function

The main.nas file shall contain a function main() that will be called on load with either the module object as parameter or the parameters passed to the module.load() function.

unload() function

If you want the module to be re-/un-loadable, you must make sure to track resources and remove them on onload. For this the main.nas shall contain a function unload() that removes any resources which were created by the module.

Example: any canvas created by the module should have called its del() method here.

Note  Calls to setlistener() and maketimer() are automatically tracked by the Module class for you, timers will be stopped automatically, listeners will be removed automatically on unload().

Rules for Module names

  1. Module names shall contain only letters (a-z, A-Z), numbers (0-9) and underscores ('_').
  2. The name must contain at least one letter so it cannot be confused with a number
  3. The name shall not match any existing .nas file or directory in FGDATA/Nasal to avoid namespace clashes
Note

Reloadable modules shipped with FlightGear are stored in subdirectories of FGDATA/Nasal/modules/ as these subdirectories will not be loaded automatically by Flightgear on start. However, modules.nas will scan this directory to create a list of available modules (list of subdirectories). Each module correspondes to one subdirectory and the direcory name is also the module name.

modules.nas

In modules.nas the class Module is defined. A module object holds information about the path and filename of the Nasal script and supports unloading and reloading the code at runtime (e.g. without restarting Flightgear as a whole) by tracking some critical resources like listeners and timers.

Note  Parts of this functionality were added to the addons manager earlier and have now been extracted to avoid code duplication.
Caution  Within a module setlistener() is overloaded and the default value for the 4th argument (runtime) is changed to 0, so the listener will run only if the property value has changed.

library functions

modules.isAvailable()

modules.isAvailable(module_name);

This function returns true, if there is a module (subdirectory) in FGDATA/Nasal/modules/ with the given name.

module_name
The name of the module (=subdirectory in Nasal/modules) to load.

Example

if (modules.isAvailable("foo_bar")) {
    modules.load("foo_bar");
}

modules.setDebug()

modules.setDebug(module_name, [debug=1]);

This function enables debugging for a module. It must be called before load()!

module_name
The name of the module (=subdirectory in Nasal/modules) to load.
debug
Defaults to 1 (true), use 0 to disable debug. If debug > 1, each listener hit will be printed, be careful not to listen to rapidly changing props, do not set runtime=1 in setlistener().

Example

var debug = 1;
modules.setDebug("foo_bar", debug);

modules.load()

modules.load(module_name, [namespace_name]);

This function attempts to load a module from FGDATA/Nasal/modules/

module_name
The name of the module (=subdirectory in Nasal/modules) to load.
namespace_name
Optional, load module to a different namespace.

Example

modules.load("foo_bar");

Methods of class Module

setFilePath()

mymod.setFilePath(path);

Configure where to look for the main file, e.g. aircraft (sub-)directory.

path
File path where the module is stored.

setMainFile()

mymod.setMainFile(filename);

Configure the nasal file to load.

filename
File that will be loaded by load().

Example

# if you develop a new nasal system for your aircraft, it might be handy to implement it as module
# so you can reload the file quickly without restarting FlightGear
var my_foo_sys = modules.Module.new("my_aircraft_foo");
my_foo_sys.setDebug(1);
my_foo_sys.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal");
my_foo_sys.setMainFile("foo.nas");
my_foo_sys.load();

setNamespace()

mymod.setNamespace(namespace);

Configure the Nasal namespace to use. Be really carefull when using existing namespaces! unload() or reload() will destroy them!

namespace
The Nasal namespace the module code will be loaded into.


load()

mymod.load([args]);

This function attempts to load the module into its namespace.

optional args
Arguments are passed to the main() function of the module. If empty, the module object will be passed to main().

unload()

mymod.unload();

This function attempts to remove tracked resources and remove the module by killing its namespace.

reload()

mymod.reload();

Shorthand, calls unload() and load().

get()

mymod.get(var_name);

Returns a variable from modules namespace.

var_name
The variable to get.

Example

var foo = modules.load("foo");
var bar = foo.get("bar"); # get variable "bar" defined in FGDATA/Nasal/modules/foo/main.nas (or a file included by this file)

Existing modules

Module name Desctiption time added
canvas_efis framework to manage canvas based EFIS screens 01/2020
canvas_draw library of drawing functions for canvas 01/2020