Difference between revisions of "Modules.nas"

From FlightGear wiki
Jump to: navigation, search
(Existing modules)
(Methods of class Module)
 
(15 intermediate revisions by 4 users not shown)
Line 1: Line 1:
= Nasal runtime re-loadable modules =
+
== Nasal runtime re-loadable modules ==
== Introduction and motivation ==
+
=== Introduction and motivation ===
Nasal (Flightgears integrated scripting language) comes with a limited number of [[Nasal library|core functions]].
+
The embedded scripting language of FlightGear, [[Nasal]] comes with a limited number of [[Nasal library|core functions]].
The language was [[Howto:Extend Nasal|extended by C++ functions]], as well as libraries written in Nasal (e.g. [[Nasal library/props|props]], [[Nasal library/io|io]], [[Nasal library/math|math]], [[Nasal library/debug|debug]] ...).  
+
  
The latter are stored in the [[Fgdata]] repository under /Nasal and loaded automatically by FlightGear.
+
The language has been [[Howto:Extend Nasal|extended by C++ functions]], as well as by libraries written in Nasal (for example [[Nasal library/props|props]], [[Nasal library/io|io]], [[Nasal library/math|math]], [[Nasal library/debug|debug]] etc).  
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]].
+
The latter are stored in the [[FGData]] repository under <code>/Nasal</code> and loaded automatically by FlightGear.
  
== Differences between add-ons and modules ==
+
Optional code, which is loaded only on demand, can be handled either by the legacy module system implemented in C++ or by <code>modules.nas</code> if run-time re-load support is required.
While there are many similarities between add-ons and modules, some words on the differences:
+
  
Modules are distributed with FlightGear as part of Fgdata.
+
For more information on how and when these files are loaded, see [[Nasal Initialization]].
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.
+
=== Differences between add-ons and modules ===
Add-ons are selected by the user before launching FlightGear, thus they may or may not be available at runtime.
+
While there are many similarities between add-ons and modules, there are also some differences:
  
 +
; Distribution
 +
: Modules are distributed with FlightGear as part of FGData or they are aircraft specific and delivered with the aircraft.
 +
: Add-ons have to be downloaded separately by a FlightGear user from wherever the author of the add-on publishes the add-on.
  
= HowTo / Examples =
+
; Loading
 +
: Modules can be loaded for example 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.
 +
 
 +
== HOWTOs and examples ==
 
First some examples, the API documentation follows below.
 
First some examples, the API documentation follows below.
  
== Nasal modules in an aircraft (e.g. for development) ==
+
=== Nasal modules in an aircraft ===
Nasal code for an aircraft is usually loaded by a XML declaration in the <aircraft>-set.xml file like this:
+
Nasal code for an aircraft (for example for development) is usually loaded by a XML declaration in the <code>[[Aircraft-set.xml]]</code> file like this:
 
<syntaxhighlight lang="xml">
 
<syntaxhighlight lang="xml">
 
<PropertyList>
 
<PropertyList>
Line 41: Line 44:
  
 
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.  
 
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,  
+
For this case you could consider to use the Module class (defined in <code>FGDATA/Nasal/modules.nas</code>) 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.
 
which means you do not have to restart whole FlightGear just for a few lines of edited Nasal code.
  
'''Example Nasal/efis_module.nas:'''
+
Example <code>Nasal/efis_module.nas</code>:
 
<syntaxhighlight lang="nasal">
 
<syntaxhighlight lang="nasal">
 
#-- load EFIS as reloadable module
 
#-- load EFIS as reloadable module
Line 54: Line 57:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
==== Menu item for reloading ====
 
Now add a menu item for easy reloading like this:
 
Now add a menu item for easy reloading like this:
{{note|
+
{{note|The module name passed to the fgcommand must match the name passed to <code>modules.Module.new()</code>}}
The module name in the property path must match the name passed to modules.Module.new()  
+
|margin=10px |width=50%}}
+
 
<syntaxhighlight lang="xml">
 
<syntaxhighlight lang="xml">
 
<menubar>
 
<menubar>
 
     <default>
 
     <default>
         <menu n=100>
+
         <menu n="100">
 
             <!-- normal aircraft menu -->
 
             <!-- normal aircraft menu -->
 
         </menu>
 
         </menu>
         <menu n=101>
+
         <menu n="101">
 
             <label>Aircraft Development</label>
 
             <label>Aircraft Development</label>
 
             <item>
 
             <item>
 
                 <label>Reload EFIS</label>
 
                 <label>Reload EFIS</label>
 
                 <binding>
 
                 <binding>
                     <command>property-assign</command>
+
                     <command>nasal-module-reload</command>
                     <property>/nasal/modules/myAircraft_EFIS/reload</property>
+
                     <module>myAircraft_EFIS</module>
                    <value>1</value>
+
 
                 </binding>
 
                 </binding>
 
             </item>
 
             </item>
Line 79: Line 80:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
{{tip|
+
{{tip|You can also call <code>myAircraft.my_efis.reload();</code> to trigger the reload. <code>myAircraft</code> is the namespace given in the <code>Aircraft-set.xml</code> file, <code>my_efis</code> is the module object (see above).}}
You can also call {{code| 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).  
+
|margin=10px |width=50%}}
+
  
= Structure of Nasal reloadable modules =
+
=== Nasal frameworks ===
A module must contain at least the file '''main.nas''' (default, other filename is possible) and should contain the functions {{code|main()}} and {{code|unload()}}.
+
Another use case for <code>Modules.nas</code> is frameworks that need reload support.
  
=== main() function ===
+
A framework usually provides more or less generic functionality that has to be adapted (configured) for a specific use case (for example a specific aircraft).
The main.nas file shall contain a function {{code|main()}} that will be called on load with either the module object as parameter or the parameters passed to the {{code|module.load()}} function.
+
  
=== unload() function ===
+
Reload support may become very convenient, if the customizing based on such framework involves many iterations of edit-reload-test.
 +
 
 +
First living example is the <code>canvas_efis</code> framework which can be included into an aircraft with a single line of code:
 +
 
 +
var efis_module = modules.load("canvas_efis");
 +
 
 +
(see also last chapter at the end of this article)
 +
 
 +
== Structure of Nasal reloadable modules ==
 +
A module must contain at least the file <code>main.nas</code> (default, other filename is possible) and should contain the functions <code>main()</code> and <code>unload()</code>.
 +
 
 +
==== main() function ====
 +
The <code>main.nas</code> file must contain a <code>main()</code> function that will be called on load with either the module object as parameter or the parameters passed to the <code>module.load()</code> 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.
 
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 {{code|unload()}} that removes any resources which were created by the module.
+
For this the <code>main.nas</code> shall contain a function <code>unload()</code> that removes any resources which were created by the module.
  
'''Example:''' any canvas created by the module should have called its {{code|del()}} method here.
+
'''Example:''' any canvas created by the module should have called its <code>del()</code> method here.
  
{{note|Calls to {{code|setlistener()}} and {{code|maketimer()}} are automatically tracked by the Module class for you, timers will be stopped automatically, listeners will be removed automatically on {{code|unload()}}.}}
+
{{note|Calls to <code>setlistener()<code> and <code>maketimer()<code> are automatically tracked by the <code>Module</code> class for you, timers will be stopped automatically, listeners will be removed automatically on <code>unload()<code>.}}
  
=== Rules for Module names ===
+
==== Rules for Module names ====
# Module names shall contain only letters (a-z, A-Z), numbers (0-9) and underscores ('_').
+
# Module names shall contain only letters (<code>a-z, A-Z</code>), numbers (<code>0-9</code>) and underscores (<code>_</code>).
 
# The name must contain at least one letter so it cannot be confused with a number
 
# The name must contain at least one letter so it cannot be confused with a number
# The name shall not match any existing .nas file or directory in FGDATA/Nasal to avoid namespace clashes
+
# The name shall not match any existing Nasal file (<code>.nas</code>) or directory in <code>FGDATA/Nasal</code> to avoid namespace clashes
  
{{note|
+
{{note|Reloadable modules shipped with FlightGear are stored in subdirectories of <code>FGDATA/Nasal/modules/</code> as these subdirectories will not be loaded automatically by FlightGear on start.
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).  
+
However, <code>modules.nas</code> 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.
+
 
 +
Each module corresponds to one subdirectory and the directory name is also the module name.
 
}}
 
}}
  
= modules.nas =
+
== modules.nas ==
In ''modules.nas'' the class ''Module'' is defined.  
+
In <code>modules.nas</code> the class <code>Module</code> 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
 
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|listeners]] and [[Timers|timers]].
+
(for example 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|addons]] manager earlier and have now been extracted to avoid code duplication.}}
+
{{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 {{code|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.
+
{{caution|Within a module <code>setlistener()</code> is overloaded and the default value for the 4th argument (runtime) is changed to <code>0</code>, so the listener will run only if the property value has changed.}}
}}
+
  
== library functions ==
+
=== library functions ===
=== modules.isAvailable() ===
+
==== modules.isAvailable() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = modules.isAvailable(module_name);
 
|syntax = modules.isAvailable(module_name);
|text = This function returns true, if there is a module (subdirectory) in FGDATA/Nasal/modules/ with the given name.
+
|text = This function returns true, if there is a module (subdirectory) in <code>FGDATA/Nasal/modules/</code> with the given name.
 
|param1 = module_name
 
|param1 = module_name
|param1text = The name of the module (=subdirectory in Nasal/modules) to load.
+
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.
 
|example1 =  
 
|example1 =  
 
if (modules.isAvailable("foo_bar")) {
 
if (modules.isAvailable("foo_bar")) {
Line 131: Line 144:
 
}}
 
}}
  
=== modules.setDebug() ===
+
==== modules.setDebug() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = modules.setDebug(module_name, [debug=1]);
 
|syntax = modules.setDebug(module_name, [debug=1]);
|text = This function enables debugging for a module. It must be called '''before''' load()!
+
|text = This function enables debugging for a module. It must be called '''before''' <code>load()</code>!
 
|param1 = module_name
 
|param1 = module_name
|param1text = The name of the module (=subdirectory in Nasal/modules) to load.
+
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.
 
|param2 = debug
 
|param2 = debug
|param2text = 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().
+
|param2text = Defaults to <code>1</code> (true), use <code>0</code> to disable debug. If debug > <code>1</code>, each listener hit will be printed, be careful not to listen to rapidly changing props, do not set <code>runtime=1</code> in <code>setlistener()</code>.
 
|example1 =  
 
|example1 =  
 
var debug = 1;
 
var debug = 1;
Line 144: Line 157:
 
}}
 
}}
  
=== modules.load() ===
+
==== modules.load() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = modules.load(module_name, [namespace_name]);
 
|syntax = modules.load(module_name, [namespace_name]);
|text = This function attempts to load a module from FGDATA/Nasal/modules/
+
|text = This function attempts to load a module from <code>FGDATA/Nasal/modules/</code>
 
|param1 = module_name
 
|param1 = module_name
|param1text = The name of the module (=subdirectory in Nasal/modules) to load.
+
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.
 
|param2 = namespace_name
 
|param2 = namespace_name
 
|param2text = Optional, load module to a different namespace.
 
|param2text = Optional, load module to a different namespace.
Line 156: Line 169:
 
}}
 
}}
  
== Methods of class Module ==
+
=== Methods of class Module ===
=== setFilePath() ===
+
==== setFilePath() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = mymod.setFilePath(path);
 
|syntax = mymod.setFilePath(path);
|text = Configure where to look for the main file, e.g. aircraft (sub-)directory.
+
|text = Configure where to look for the main file, for example in the aircraft (sub-)directory.
 
|param1 = path
 
|param1 = path
 
|param1text = File path where the module is stored.
 
|param1text = File path where the module is stored.
 
}}
 
}}
  
=== setMainFile() ===
+
==== setMainFile() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = mymod.setMainFile(filename);
 
|syntax = mymod.setMainFile(filename);
 
|text = Configure the nasal file to load.
 
|text = Configure the nasal file to load.
 
|param1 = filename
 
|param1 = filename
|param1text = File that will be loaded by load().
+
|param1text = File that will be loaded by <code>load()</code>.
 
|example1 =
 
|example1 =
 
# if you develop a new nasal system for your aircraft, it might be handy to implement it as module
 
# if you develop a new nasal system for your aircraft, it might be handy to implement it as module
Line 181: Line 194:
 
}}
 
}}
  
=== setNamespace() ===
+
==== setNamespace() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = mymod.setNamespace(namespace);
 
|syntax = mymod.setNamespace(namespace);
|text = Configure the Nasal namespace to use. Be really carefull when using existing namespaces! unload() or reload() will destroy them!
+
|text = Configure the Nasal namespace to use. Be really careful when using existing namespaces! <code>unload()</code> or <code>reload()</code> will destroy them!
 
|param1 = namespace
 
|param1 = namespace
 
|param1text = The Nasal namespace the module code will be loaded into.
 
|param1text = The Nasal namespace the module code will be loaded into.
 
}}
 
}}
  
 +
==== setlistenerRuntimeDefault() ====
 +
{{Nasal doc
 +
|syntax = mymod.setlistenerRuntimeDefault(i);
 +
|text = This changes the default setlistener behaviour for this module regarding 4th ('runtime') argument. FG default is 1 (=run listener every time the prop is written to), but you might want a default of 0 for your module (= run listener only, if value has changed). This is kind of a convenience function. It is better to explicitly specify the desired parameters when calling setlistener().
 +
|param1 = i
 +
|param1text = integer, 0..2 define the default value to setlister 4th parameter if not specified explicitly
 +
}}
 +
 +
==== setDebug() ====
 +
{{Nasal doc
 +
|syntax = mymod.setDebug(debug=1);
 +
|text = Activate debugging for this module. '''Must be called before calling <code>load()</code>!'''
 +
|param1 = debug
 +
|param1text =
 +
0: no debugging;
 +
1 (default if no argument given): print calls to redirected <code>setlister()</code> and <code>maketimer()</code>;
 +
 +
2: listeners print property path when hit (Use with caution! '''Do not call <code>setlistener()</code> with <code>runtime=1</code>'''.)
 +
}}
  
=== load() ===
+
==== load() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = mymod.load([args]);
 
|syntax = mymod.load([args]);
 
|text = This function attempts to load the module into its namespace.
 
|text = This function attempts to load the module into its namespace.
 
|param1 = optional args
 
|param1 = optional args
|param1text = Arguments are passed to the main() function of the module. If empty, the module object will be passed to main().
+
|param1text = Arguments are passed to the <code>main()</code> function of the module. If empty, the module object will be passed to <code>main()</code>.
 
}}
 
}}
  
=== unload() ===
+
==== unload() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = mymod.unload();
 
|syntax = mymod.unload();
Line 204: Line 236:
 
}}
 
}}
  
=== reload() ===
+
==== reload() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = mymod.reload();
 
|syntax = mymod.reload();
|text = Shorthand, calls unload() and load().
+
|text = Shorthand, calls <code>unload()</code> and <code>load()</code>.
 
}}
 
}}
  
=== get() ===
+
==== get() ====
 
{{Nasal doc
 
{{Nasal doc
 
|syntax = mymod.get(var_name);
 
|syntax = mymod.get(var_name);
|text = Returns a variable from modules namespace.
+
|text = Returns a variable from the modules namespace.
 
|param1 = var_name
 
|param1 = var_name
 
|param1text = The variable to get.
 
|param1text = The variable to get.
Line 221: Line 253:
 
}}
 
}}
  
= Existing modules with reload support =
+
== Property tree interface for modules.nas ==
Stable Nasal frameworks which support reloading can be added to ''FGDATA/Nasal/modules/<module_name>''.  
+
In the property tree there is a subtree <code>/nasal</code> to control modules and get some statistics.
 +
 
 +
The properties available depend on the type of module ("load-once" or "reloadable", see [[Nasal Initialization]] for more information on the differences).
 +
 
 +
=== Reloadable modules / frameworks ===
 +
Modules handled by <code>modules.nas</code> will have their properties in <code><nowiki>/nasal/modules/<moduleName></nowiki></code> where <code><nowiki><moduleName></nowiki></code> is given by the developer when calling either <code><nowiki>Module.new("<moduleName>")</nowiki></code> or <code><nowiki>modules.load("<moduleName>")</nowiki></code>.
 +
 
 +
In the latter case <code><nowiki><moduleName></nowiki></code> specifies the subdirectory <code><nowiki>FGDATA/Nasal/modules/<moduleName></nowiki></code> in which some framework is stored.
 +
 
 +
{| class="wikitable"
 +
|-
 +
! property !! type !! content
 +
|-
 +
| <code>loaded</code> || bool || true if module was loaded without errors
 +
|-
 +
| <code>reload</code> || bool || to trigger reload from the property browser. For menues, XML bindings and scripts the fgcommand is the prefered way to trigger reload.
 +
|-
 +
| <code>listeners</code> || int || Number of tracked listeners
 +
|-
 +
| <code>listener-hits</code> || int || If debugging is enabled, this prop shows the total number of hits to all tracked listeners.
 +
|-
 +
| <code>timers</code> || int || Number of tracked timers (maketimer).
 +
|}
 +
 
 +
=== Legacy load-once modules ===
 +
A legacy load-once module is a direct (1st level) subdirectory of <code>FGDATA/Nasal/</code> and its corresponding property tree is <code><nowiki>/nasal/<moduleName>/</nowiki></code> where <code><nowiki><moduleName></nowiki></code> equals the name of the subdirectory.
 +
 
 +
It is handled by C++ code and must have a corresponding entry in <code>FGDATA/defaults.xml</code> which defines a property "enabled" and optionally a property "module".
 +
 
 +
If enabled is set to false in <code>defaults.xml</code>, the C++ code will setup a listener and load the module as soon as enabled is set to true.
 +
 
 +
The property name "module" is a bit misleading, it is used to define into which namespace the files shall be loaded.
 +
 
 +
For each Nasal file in the subdirectory a <code>file[i]</code> property is created holding the full path+filename.
 +
 
 +
The bool <code>loaded</code> property shows the status of the module.
 +
 
 +
== Existing modules with reload support ==
 +
Stable Nasal frameworks which support reloading can be added to <code>FGDATA/Nasal/modules/<module_name></code>.  
 
This allows an aircraft developer to configure the framework for a specific aircraft and make use of the reload magic while developing the configuration.
 
This allows an aircraft developer to configure the framework for a specific aircraft and make use of the reload magic while developing the configuration.
  
Line 229: Line 299:
 
! Module name !! Desctiption !! time added  
 
! Module name !! Desctiption !! time added  
 
|-
 
|-
| [[Canvas EFIS Framework|canvas_efis]] || framework to manage canvas based EFIS screens || 02/2020
+
| [[Canvas EFIS Framework|<code>canvas_efis</code>]] || framework to manage canvas based EFIS screens || 02/2020
 
|}
 
|}
 +
 +
[[Category:Nasal]]

Latest revision as of 13:25, 17 May 2020

Nasal runtime re-loadable modules

Introduction and motivation

The embedded scripting language of FlightGear, Nasal comes with a limited number of core functions.

The language has been extended by C++ functions, as well as by libraries written in Nasal (for example props, io, math, debug etc).

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, there are also some differences:

Distribution
Modules are distributed with FlightGear as part of FGData or they are aircraft specific and delivered with the aircraft.
Add-ons have to be downloaded separately by a FlightGear user from wherever the author of the add-on publishes the add-on.
Loading
Modules can be loaded for example 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.

HOWTOs and examples

First some examples, the API documentation follows below.

Nasal modules in an aircraft

Nasal code for an aircraft (for example for development) 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();

Menu item for reloading

Now add a menu item for easy reloading like this:

Note  The module name passed to the fgcommand 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>nasal-module-reload</command>
                    <module>myAircraft_EFIS</module>
                </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).

Nasal frameworks

Another use case for Modules.nas is frameworks that need reload support.

A framework usually provides more or less generic functionality that has to be adapted (configured) for a specific use case (for example a specific aircraft).

Reload support may become very convenient, if the customizing based on such framework involves many iterations of edit-reload-test.

First living example is the canvas_efis framework which can be included into an aircraft with a single line of code:

var efis_module = modules.load("canvas_efis");

(see also last chapter at the end of this article)

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 must contain a main() function 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 Nasal file (.nas) 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 corresponds to one subdirectory and the directory 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 (for example 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 (in the 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 (in the 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 (in the 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, for example in the 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 careful when using existing namespaces! unload() or reload() will destroy them!

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

setlistenerRuntimeDefault()

mymod.setlistenerRuntimeDefault(i);

This changes the default setlistener behaviour for this module regarding 4th ('runtime') argument. FG default is 1 (=run listener every time the prop is written to), but you might want a default of 0 for your module (= run listener only, if value has changed). This is kind of a convenience function. It is better to explicitly specify the desired parameters when calling setlistener().

i
integer, 0..2 define the default value to setlister 4th parameter if not specified explicitly

setDebug()

mymod.setDebug(debug=1);

Activate debugging for this module. Must be called before calling load()!

debug
0: no debugging;

1 (default if no argument given): print calls to redirected setlister() and maketimer();

2: listeners print property path when hit (Use with caution! Do not call setlistener() with runtime=1.)

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 the 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)

Property tree interface for modules.nas

In the property tree there is a subtree /nasal to control modules and get some statistics.

The properties available depend on the type of module ("load-once" or "reloadable", see Nasal Initialization for more information on the differences).

Reloadable modules / frameworks

Modules handled by modules.nas will have their properties in /nasal/modules/<moduleName> where <moduleName> is given by the developer when calling either Module.new("<moduleName>") or modules.load("<moduleName>").

In the latter case <moduleName> specifies the subdirectory FGDATA/Nasal/modules/<moduleName> in which some framework is stored.

property type content
loaded bool true if module was loaded without errors
reload bool to trigger reload from the property browser. For menues, XML bindings and scripts the fgcommand is the prefered way to trigger reload.
listeners int Number of tracked listeners
listener-hits int If debugging is enabled, this prop shows the total number of hits to all tracked listeners.
timers int Number of tracked timers (maketimer).

Legacy load-once modules

A legacy load-once module is a direct (1st level) subdirectory of FGDATA/Nasal/ and its corresponding property tree is /nasal/<moduleName>/ where <moduleName> equals the name of the subdirectory.

It is handled by C++ code and must have a corresponding entry in FGDATA/defaults.xml which defines a property "enabled" and optionally a property "module".

If enabled is set to false in defaults.xml, the C++ code will setup a listener and load the module as soon as enabled is set to true.

The property name "module" is a bit misleading, it is used to define into which namespace the files shall be loaded.

For each Nasal file in the subdirectory a file[i] property is created holding the full path+filename.

The bool loaded property shows the status of the module.

Existing modules with reload support

Stable Nasal frameworks which support reloading can be added to FGDATA/Nasal/modules/<module_name>. This allows an aircraft developer to configure the framework for a specific aircraft and make use of the reload magic while developing the configuration.

Module name Desctiption time added
canvas_efis framework to manage canvas based EFIS screens 02/2020