Modules.nas: Difference between revisions

Jump to navigation Jump to search
1,053 bytes added ,  14 April 2020
Copy editing
m (+-headings: All headings down one level, level 1 (one equal sign) should not be used in articles. It is used by the wiki software for the article title.)
(Copy editing)
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 mbedded 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 have 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.
 
The latter are stored in the [[FGData]] repository under <code>/Nasal</code> 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 <code>modules.nas</code> if run-time re-load support is required.


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


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


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


Modules can be loaded e.g. by an aircraft if the aircraft developer wants to make use of the module.
; Loading
Add-ons are selected by the user before launching FlightGear, thus they may or '''may not be available''' at runtime.
: 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.


== HowTo / Examples ==
== 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 40: 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 58:


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 in the property path 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>
Line 78: 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%}}


=== Nasal frameworks ===
=== Nasal frameworks ===
Another usecase for Modules.nas is frameworks that need reload support.  
Another use case for <code>Modules.nas</code> is frameworks that need reload support.
A framework usually provides more or less generic functionality that has to be adapted (configured) for a specific usecase (e.g. a specific aircraft).
 
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.
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:  
First living example is the <code>canvas_efis</code> framework which can be included into an aircraft with a single line of code:  


{{code|
var efis_module = modules.load("canvas_efis");
<nowiki>var efis_module = modules.load("canvas_efis");</nowiki>
}}


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


= Structure of Nasal reloadable modules =
== 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 {{code|main()}} and {{code|unload()}}.
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 ====
==== main() function ====
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.
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 ====
==== 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|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.
 
However, <code>modules.nas</code> will scan this directory to create a list of available modules (list of subdirectories).


{{note|
Each module corresponds to one subdirectory and the directory name is also the module name.
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 ==
== 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 ===
Line 134: Line 135:
{{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 146: Line 147:
{{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 159: Line 160:
{{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 172: Line 173:
{{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.
Line 182: Line 183:
|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 196: Line 197:
{{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.
Line 204: Line 205:
{{Nasal doc
{{Nasal doc
|syntax = mymod.setDebug(debug=1);
|syntax = mymod.setDebug(debug=1);
|text = Activate debugging for this module. '''Must be called before calling load()!'''
|text = Activate debugging for this module. '''Must be called before calling <code>load()</code>!'''
|param1 = debug
|param1 = debug
|param1text =  
|param1text =  
0: no debugging;  
0: no debugging;  
1 (default if no argument given): print calls to redirected {{code|setlister()}} and {{code|maketimer()}};  
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()}} with runtime=1.)
2: listeners print property path when hit (Use with caution! '''Do not call <code>setlistener()</code> with <code>runtime=1</code>'''.)
}}
}}


Line 218: Line 219:
|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>.
}}
}}


Line 230: Line 231:
{{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>.
}}
}}


Line 236: Line 237:
{{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 245: Line 246:


== Property tree interface for modules.nas ==
== Property tree interface for modules.nas ==
In the property tree there is a subtree /nasal to control modules and get some statistics.
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).
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 ===
=== 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  
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>.
{{code|Module.new("<moduleName>")}} or {{code|modules.load("<moduleName>")}}.  
 
In the latter case <moduleName> specifies the subdirectory FGDATA/Nasal/modules/<moduleName> in which some framework is stored.
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"
{| class="wikitable"
Line 257: Line 259:
! property !! type !! content
! property !! type !! content
|-
|-
| loaded || bool || true if module was loaded without errors
| <code>loaded</code> || bool || true if module was loaded without errors
|-
|-
| reload || bool || set this to true to trigger reload
| <code>reload</code> || bool || set this to true to trigger reload
|-
|-
| listeners || int || Number of tracked listeners
| <code>listeners</code> || int || Number of tracked listeners
|-
|-
| listener-hits || int || If debugging is enabled, this prop shows the total number of hits to all tracked listeners.
| <code>listener-hits</code> || int || If debugging is enabled, this prop shows the total number of hits to all tracked listeners.
|-
|-
| timers || int || Number of tracked timers (maketimer).
| <code>timers</code> || int || Number of tracked timers (maketimer).
|}
|}


=== Legacy load-once modules ===
=== 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.  
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 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.
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.
The property name "module" is a bit misleading, it is used to define into which namespace the files shall be loaded.


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


The bool "loaded" property shows the status of the module.
The bool <code>loaded</code> property shows the status of the module.


== Existing modules with reload support ==
== Existing modules with reload support ==
Stable Nasal frameworks which support reloading can be added to ''FGDATA/Nasal/modules/<module_name>''.  
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 286: Line 291:
! 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
|}
|}

Navigation menu