13,212
edits
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 === | ||
The mbedded scripting language of FlightGear, [[Nasal]] comes with a limited number of [[Nasal library|core functions]]. | |||
The latter are stored in the [[ | 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 | While there are many similarities between add-ons and modules, there are also some differences: | ||
Modules are distributed with FlightGear as part of | ; 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 | ; 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. | |||
== | == HOWTOs and examples == | ||
First some examples, the API documentation follows below. | First some examples, the API documentation follows below. | ||
=== Nasal modules in an aircraft | === Nasal modules in an aircraft === | ||
Nasal code for an aircraft is usually loaded by a XML declaration in the < | 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 <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() | |||
<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 | |||
=== Nasal frameworks === | === Nasal frameworks === | ||
Another | 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 | |||
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: | ||
var efis_module = modules.load("canvas_efis"); | |||
(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 | 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 | 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 | 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 | '''Example:''' any canvas created by the module should have called its <code>del()</code> method here. | ||
{{note|Calls to | {{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 | # 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). | |||
Each module corresponds to one subdirectory and the directory name is also the module name. | |||
Each module | |||
}} | }} | ||
== modules.nas == | == modules.nas == | ||
In | 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 | ||
( | (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 [[ | {{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 | {{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 ( | |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 ( | |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 ( | |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, | |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 | |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 | 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 | 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>. | ||
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 | For each Nasal file in the subdirectory a <code>file[i]</code> property is created holding the full path+filename. | ||
The bool | 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 | 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 | ||
|} | |} |