Canvas ND framework

From FlightGear wiki
Jump to navigation Jump to search
Canvasready.png
NavDisplay
MAP mode with a SID from Eindhoven Airport
MAP mode with a SID from Eindhoven Airport
Started in 11/2013
Description Canvas/Nasal driven NDs
Maintainer(s) Gijs (B747), Hyde (B777), Soitanen (B737NG), (Hooray, Philosopher & D-LEON via MapStructure)
Contributor(s) User:Gijs (since 11/2013)
Status Under active development as of 12/2013

The navigation display (ND) is a display in a glass cockpit, providing information about an aircraft's lateral situation. As of late 2013, a new fully-scripted ND (Navigation display being the old, deprecated one [1]) is in development for FlightGear using the new MapStructure back-end for charting purposes, and SVG symbols animated via Nasal functions for any non-mapping aspects (typically using listeners and timers).

Thanks to Canvas, much more complex and sophisticated graphics are possible now, without having to know any C++, and without having to rebuild FlightGear from source. Anybody able to run FlightGear and a text editor, can now easily use and extend the ND framework, or add support for new types (styles) of NDs (think Citation, MD etc).

Extending the framework should normally require very little in terms of coding - typically, people can simply take an existing feature/symbol, copy & paste the code section and customize it according to their needs (e.g. different SVG element id, different animations, different properties) - symbols can be easily changed by providing a separate SVG file, or by creating a new one via Inkscape.

Usually, it is a good idea to treat existing "styles" as a template for creating new ones, by copying those verbatim and renaming the corresponding files/items accordingly, to come up with a new (identical) style using a different name, customizing that as needed.

The main source is located at fgdata/Nasal/canvas/map/navdisplay.mfd. Aircraft-specifics can be found in fgdata/Nasal/canvas/map/navdisplay.styles. Artwork is usually contained in a dedicated SVG file created via Inkscape.

Screen shot showing the control panel of the 777-200ER for controlling the Canvas-based NavDisplay

Adding the navigation display to your aircraft

The new ND is available in FlightGear 3.0 as of February 2014.

  1. Add a surface object to your 3D model and apply a place holder texture to it. You can refer to the 747-400 and 777 for examples.
  2. Copy the ND.nas file of the Boeing 777 into your own aircraft directory (Aircraft/777/Models/Instruments/ND/ND.nas).
  3. Add the file to the <nasal> block in your aircraft's -set.xml file.
  4. In order to customize the ND for your specific aircraft, change a few options in your ND.nas file:
    1. the node behind addPlacement is the name of the object in your 3D model. For multiple independent NDs, each ND should have its own object (and thus placement node). The 777 for example has a "ndScreenL" and a "ndScreenR" (for left/right screens respectively).
    2. Rename the properties in the "myCockpit_switches" hash at the top of the ND.nas file to match your own properties (range selector, mode selector etc.), see Aircraft/747-400/Models/Cockpit/Instruments/ND/ND.nas. You're advised to use the defaults (and change other systems of your aircraft accordingly). For an overview of switches, see #Cockpit switches.
  5. Add knobs/buttons to the 3D model of your cockpit to control the various options of the ND. See Aircraft/777/Models/Instruments/EFIS/efis-ctl1.xml for an example.
  6. Add a binding to your screen(s) to open the ND in a popup dialog. See Aircraft/777/Models/Instruments/ND/ND_L.xml for an example.

That's the only thing you need to do right now. future changes will be in navdisplay.mfd and will be automatically available to you once they're committed/merged, no changes needed in your ND.nas at all, except for additional switches/buttons that may be created, and which would obviously need to be mapped to your cockpit hot spots.

If you are facing problems with the steps outlined above, please get in touch via the article's talk page or the forum, so that the instructions can be improved accordingly. Thank you!

A more detailed step-by-step guide can be found below.

Cockpit switches

Note  The default property is relative to the root location of your ND, it is NOT an absolute path. Boolean values (true/false) can also be just 1/0. The list of switches below is correct for FG 3.0 - future versions may support additional switches. In the future, we're also going to make the ND style-able this way, so that colors, fonts, size (LOD) and symbols can be customized easily via ND.nas. FlightGear 3.1 introduces an optional hash argument called options which is a hash that supports layer-specific options (more details to follow)
Switch Description Default property Accepted values new in 3.1+
toggle_airports Display airports on the map /inputs/arpt true, false
toggle_centered Center ND modes /inputs/nd-centered true, false
toggle_data Display way point data on the map /inputs/data true, false
toggle_display_mode Change display mode /mfd/display-mode APP, MAP, PLAN, VOR
toggle_display_type Change display type /mfd/display-type CRT, LCD
toggle_lh_vor_adf Left navigation (-1 = ADF, 0 = OFF, 1 = VOR) /inputs/lh-vor-adf -1, 0, 1
toggle_position Display position data on the map (currently unused) /inputs/pos true, false
toggle_range Range of the maps in nautical miles /inputs/range-nm Any positive number
toggle_rangearc Display range arcs /mfd/rangearc true,false
toggle_rh_vor_adf Right navigation (-1 = ADF, 0 = OFF, 1 = VOR) /inputs/rh-vor-adf -1, 0, 1
toggle_stations Show/hide stations (VOR/DME/NDB) /inputs/sta true/false options:{ radios:[0,1] } (WIP)
toggle_terrain Terrain radar (currently unused) /inputs/terr true, false
toggle_track_heading toggle track/heading mode (new in 3.1+) /trk-selected true, false
toggle_traffic Display TCAS data /inputs/tfc true, false
toggle_true_north Toggle true or magnetic north /mfd/true-north true, false
toggle_waypoints Display way points (fixes) on the map /inputs/wpt true, false
toggle_weather Weather radar /inputs/wxr true, false

Aircraft Support

For the framework to become sufficiently generalized and aircraft-agnostic, we need many different aircraft to adopt it and aircraft developers to provide feedback regarding missing features. We are looking for people interested in integrating the new system, so that it can be better developed with different requirements in mind.

The 747 and 777 are the primary reference examples for now. The following people/aircraft are currently in the process of adopting the new NavDisplay (please add any related efforts here):

  • 01/2014: Gijs (747-400) 100}% completed
  • 01/2014: Hyde (777-200) 100}% completed
  • 02/2014: mueko (A320) 70}% completed [1]
  • 08/2014: artix (A330-200/A320-neo) 100}% completed [2] (including a custom Airbus ND style) [2] [3]
  • 02/2014: Soitanen (737-300) 80}% completed [3]
  • 02/2014: 5H1N0B1 (m2000-5) 100}% completed
  • 09/2014: cain071546 (707) [4] 10}% completed
  • 05/2015: tikibar (747-8) 100}% completed
  • 05/2015: tikibar (757-200) 100}% completed
  • 12/2015: tikibar (767-300) 100}% completed
  • 05/2016: DrDavid, (MD-10) 100}% completed
  • 05/2016: JWOCKY (A310) 60}% completed
  • 05/2016: Octal450, J Maverick 16 (KC-137R) 100}% completed
  • 10/2016: clm76 (CitationX) 10}% completed [4]
  • 12/2016: jsb [5] (see also CRJ 700 family canvas)

If you are finding yourself having problems integrating the framework, please refer to already integrated aircraft as reference/example, or get in touch with their maintainers - ideally, via the forum, so that everybody can contribute and benefit. Please also feel free to add your own findings to this article.

Design

Hyde-777-200ER-independent-NDs.png
Cquote1.png Originally, it was a single monolithic update() method that would basically be called at frame rate using a settimer/maketimer callback.

When things were restructured, the conditional parts of this huge method were moved into predicates that reside in a huge hash: https://gitorious.org/fg/fgdata?p=fg:fgdata.git;a=blob;f=Nasal/canvas/map/navdisplay.styles#l14
Meanwhile, the update() method has been inflated again due to some logic that wasn't added to the hash, e.g. see: https://gitorious.org/fg/fgdata?p=fg:fgdata.git;a=blob;f=Nasal/canvas/map/navdisplay.mfd#l476

The meat of it still is in update(), and those "layers" are fairly boring meanwhile because they're separately managed by Philosopher's MapStructure framework.

But individual ND elements (canvas groups) are basically SVG-driven, i.e. SVG elements that are looked up and then updated/animated via timers and listeners: https://gitorious.org/fg/fgdata?p=fg:fgdata.git;a=blob;f=Nasal/canvas/map/navdisplay.styles;hb=f3df87622ca60eb45f359c05bd913524eeb44667#l183

The actual code evaluating each SVG predicate is still called via the update() method, but it's just a foreach loop with a few lines:
https://gitorious.org/fg/fgdata?p=fg:fgdata.git;a=blob;f=Nasal/canvas/map/navdisplay.mfd#l760

None of this is set in stone, and we're actually looking forward to generalizing this some more.


— Hooray (Fri Jun 13). Re: Event Management module for post 3.2.
(powered by Instant-Cquotes)
Cquote2.png

Custom ND Styles

Note  As of early 2015 the following section is slightly outdated: Artix has recently created an Airbus specific ND style and we're hoping to see additional ND styles/types to be supported over time, to help increasingly unify the framework accordingly.

Note that setup instructions above will initially just give you a Boeing-centric ND, because it is currently being developed by the 747/777 maintainers primarily - please use the issue tracker to file feature requests for missing features.

Over time, the framework will grow and become more flexible,so that also specifics of non-Boeing aircraft can be properly emulated. But obviously this will take a while. We invite aircraft developers to help extend and refine the framework accordingly.

Design-wise, the ND code still isn't quite there yet, so unless someone is already very familiar with Nasal/Canvas, I would still postpone any custom implementations - it is better to coordinate things with Gijs and Hyde to keep on improving the existing ND, rather than re-implementing an airbus-specific one at the moment. Simply because things are very much in flux still - there's quite a bit of refactoring pending, and it seems that neither of us is going to tackle this anytime soon.

We can obviously not stop anybody from working with that code, but we would very much prefer to scratch off a handful of items from our todo list first - otherwise, this is going to be a maintenance problem in the mid-term.

Working on anything listed there should help prepare the design such that the framework becomes more aircraft agnostic, i.e. to support manufacturer-specific ND types.

Currently, we are simply not there yet - and some of the recent additions aren't exactly making this much easier unfortunately.

The thing is, even if someone were to come up with a 100% correct/working A320 implementation now, the framework itself would not yet be sufficiently generalized - some of the recent additions are making things more complicated here, such as the inflated update() method, the Boeing-specific constructor and hard-coded assumptions in the newMFD() method.

None of this is really difficult to fix, but these things need to be tackled first - otherwise, we'll have too many "construction sites" eventually.

Also, working on different Boeing-centric implementations will help us generalize things a bit more over time, which will surely come in handy once supporting other airliners.

We appreciate any help in maintaining and updating these frameworks, but currently these are not yet as flexible (aircraft-agnostic) as they need to be.

Speaking in non-coding terms: When we refactored the ND code, we explicitly wanted to support different makes/models for different aircraft using a single back-end and code base. And that's something that is already prepared in the design in several places, and it may seem straightforward to people not familiar with the code.

Here, you could say that the ND itself is the 3D model, while the "style" is analogous to a "livery" (texture pack).

The way things are implemented, we prepared support for "livery packs", and for easily creating new styles without having to touch the code.

However, currently there's still some work that needs to be done, and Gijs, Hyde and Soitanen have added features that need to be integrated properly first - you could say that they're working on the "3D model" still, so it's not such a good idea to create lots of "liveries" (styles) currently. Or those would need to updated/ported later on.

So it would be better to wait a little here and provide plenty of feedback in the meantime - if you really want to, you can get involved in the ND/MapStructure efforts themselves, but I would not yet suggest to come up with a totally new ND implementation - unless you're also prepared/willing to do the necessary groundwork, i.e. Nasal-space refactoring - the latter will mostly affect the new/newMFD/update functions - so not too difficult to understand if you are interested.

Otherwise, I would just suggest to watch the whole effort - The Boeing-ND stuff still needs quite a bit of work, and once more things are completed, we can better finalize the underlying frameworks.

Gallery

Adding new MapStructure Layers

To add a new mapping layer to display custom symbols (radar, scenery objects etc), you only need to open $FG_ROOT/Nasal/canvas/map/navdisplay.styles, navigate to the vector named layers and add your new layer there, e.g. named "AA":

{ name:'AA', isMapStructure:1, update_on:['toggle_range','toggle_display_mode'],
predicate: func(nd, layer) {
 # toggle visibility here
 var visible=nd.in_mode('toggle_display_mode', ['MAP']);
 layer.group.setVisible( visible );
 if (visible) {
  #print("Updating MapStructure ND layer: AA");
  layer.update();
 }
}, # end of layer update predicate
'z-index': 1,
}, # end of AA layer

You may want to add new switches to directly toggle the layer on/off

Adding new features

Note  Please get in touch with User:Gijs before touching navdisplay.styles, and don't touch navdisplay.mfd without coordinating your work via the forum first.

If you are primarily interested in porting/adding new mapping layers (such as e.g. waypoints, route, weather etc), please see the MapStructure article for details. Otherwise, this section is focused on dealing with non-mapping aspects, i.e. ND-specific SVG symbols and animating them via properties and calculations. Typically, a new symbol will require 10-20 lines of code added to navdisplay.styles - it will typically take 5-10 minutes to add a new animated symbol to the ND.

If you want to add new features to the navdisplay.styles code, you need to open that file and map an existing SVG element to a Nasal callback routine - that's how everything is working currently. A straightforward examples can be found in the implementation of the planArcs symbol animation:

{
 id:'planArcs', # the name of the symbol in the SVG file
 impl: { # a hash containing callbacks
  init: func(nd,symbol) {}, # required intiialization code
  predicate: func(nd) { nd.in_mode('toggle_display_mode', ['PLAN']) }, # the predicate that decides whether the true/false callbacks below will be executed
  is_true: func(nd) { nd.symbols.planArcs.show() }, # this will be invoked if the predicate evaluates to true
  is_false: func(nd) { nd.symbols.planArcs.hide() }, # and this if it does not
 },
},

This is hash entry, linked to the SVG element whose id is planArcs. The embedded hash named impl contains 4 fields:

  • init(): a Nasal function that will be invoked to initialize the symbol
  • predicate(): a Nasal function that will be invoked to check some condition when updating (predicate)
  • is_true(): the Nasal function that will be executed if the condition is true
  • is_false(): the Nasal function that will be executed if the condition is false

Except for the init() callback, all others get passed a single argument called nd which is the instance of the current nd, i.e. the equivalent of me, Nasal's this pointer. In the future, we're hoping to generalize this a little better and also use the init signature, which should make for shorter/more readable code.

In this case, the predicate will merely determine in which mode a certain switch is and hide/show the symbol (SVG element) accordingly. Here, it's checking that the display-mode switch is set to PLAN mode, and otherwise hide the symbol.

Switches are configured at the top of the navdisplay.mfd file, but will usually be overridden by each aircraft-specific ND.nas, i.e. by the aircraft developer. For now, modes are hard-coded and people should refer to the code to see what's supported there.

Note  The whole layout/approach of the navdisplay.style file isn't set in stone at all - it just ended up being a simple workaround to accomplish some goals, while also allowing newcomers to easily maintain/extend the system by just having to go through a handful of lines of code. At some point this may further evolve. It would now even be straightforward to move the whole thing to XML space using a simple script that turns our hash into XML. But ultimately we hope to wait a little with that until a generic MFD framework has evolved that can also be used for other displays like a PFD, CDU or EFB.

There's a "configuration hash" called "NDStyles" at the top of the file. Each aircraft can have its own entry in NDStyles, such as NDStyles["Boeing"] or NavDisplay["Airbus"]. Then, all the required canvas callbacks are listed there, i.e. the font mapper etc.

There's also an entry for a custom SVG filename. However, we encourage people to get in touch with Gijs and Hyde to help coordinate all ND development a little - so that a common (and shared) SVG file can be used for most use-cases, this is already the case for the B744 and B777 - we're hoping to get other aircraft developers involved, to help generalize the SVG file, instead of having dozens of different files and symbols. Most features should be identical or at least very similar. Which is why it's a good idea to get in touch with other people working on the ND code.

Lines 52-131 set up existing layers, which are now shared with the GUI code - so that is where new layers are added. Aircraft specific SVG elements are added at the end, after line 132

For now, we suggest to directly use the 747-400 ND - but the framework is now prepared to easily style & customize it for different aircraft, we will probably add a tutorial to the wiki to explain how things are hanging together there. The constructor and init methods may still need some work to generalize things there, because they are basically still the original code.

So to replace the 747 ND and customize it, these are the steps:

  • add a new entry to the NDStyles hash, for example "777-200"
  • you can simply copy the configuration stuff from the 747 to get started
  • start customizing things there
  • open the SVG file in inkscape and change it as required, save it
  • change the SVG filename in your NDStyles["777-200"] hash entry to match your own file name
  • provide animation callbacks for any 777 specific elements
  • open your own copy of ND.nas
  • change the constructor call .new() to specify the name of your new NDStyles entry
  • by default, this is 747-400 for the time being, for example:
var NDCpt = ND.new("instrumentation/efis", myCockpit_switches, "777-200" );

the "777-200" part needs to match the identifier used in the NDStyles hash.

To get started, just copy the whole 747-400 section in NDStyles and rename it to match your needs. Once that is working (getting a 747-400 ND via the 777-200 identifier), you can simply replace the SVG file name and add your own update handlers to incrementally come up with your own ND.

Step by Step

Note  The following section is currently work-in-progress, it is being written by 5H1N0B1 and drdavid, please get in touch to help reviewing/improving things.

More detailed instruction. See if it could help.

  1. Have an updated version of FlightGear (V. 3.0+).
  2. Set up the 3D model in this way:

To create a 3D MFD ->

  • Select a 3D Border (border of the screen, with screws, etc.)
  • Need to have a 3D Button/switch, molette, or turnable knob
  • A 3D Screen object, with:
    • correct dimensions
    • Single texture, -> with correct dimensions
    • Correct dimension of the UVmap
  1. Copy and paste the ND.nas file to the appropriate folder (In the Mirage 2000 this was the "Center-mfd" folder).
  2. Add the ND.nas pathway to your base .xml file in the Nasal block.
  3. Edit ND.nas :
  • Rename the screen to match the object in 1)c) if needed.
  • If installing a single MFD, you do not need another object, so suppress the line (when it's calling the object itself)
  • Keep the property's name -> it's ok. You just need to input it with the .xml button in 1)b)

Now, let's add the xml itself.

Really simple, it's just playing with properties. For example, see this for the airport property:

<!--######################## Airports ############################-->
<animation>
  <type>pick</type>
  <object-name>bt-h1</object-name>
  <visible>true</visible>
  <action>
    <button>0</button>
    <binding>
      <command>property-toggle</command>
      <property>/instrumentation/efis/inputs/arpt</property>
    </binding>
  </action>
</animation>

Or here again for the range selection:

<animation>
  <type>pick</type>
  <object-name>trim1</object-name>
  <action>
    <button>4</button>
    <!--  scroll up -->
    <repeatable>false</repeatable>   
    <binding>
      <command>property-cycle</command>
      <property>/instrumentation/efis/inputs/range-nm</property>
      <value>10</value>
      <value>20</value>
      <value>40</value>
      <value>80</value>
      <value>160</value>
      <value>320</value>
    </binding>   
  </action>
</animation>

<animation>
  <type>pick</type>
  <object-name>trim1</object-name>
  <action>
    <button>3</button>
    <!--  scroll up -->
    <repeatable>false</repeatable>
    <binding>
      <command>property-cycle</command>
      <property>/instrumentation/efis/inputs/range-nm</property>
      <value>320</value>
      <value>160</value>
      <value>80</value>
      <value>40</value>
      <value>20</value>
      <value>10</value>              
    </binding>   
  </action>
</animation>

Next, you can optionally add this:

 
<animation>
  <type>pick</type>
  <object-name>blackbkd</object-name>
  <action>
    <button>0</button>
    <repeatable>false</repeatable>
    <binding>
      <command>nasal</command>
      <script>mirage2000.showNd();</script>
    </binding>
  </action>
</animation>


Where 'blackbkd' is the screen.

This last option allows you to open a dialog box with what the actual canvas should display in your 1)c) screen. This is very useful to check all the different steps when something isn't working well. However, you'll need to change mirage2000 to match the name of your own aircraft/namespace.


Normally following these steps, you should have a working MFD.

Development

Sooner or later, we will be refactoring ND related files to use a structure like the following:

  • $FG_ROOT/Nasal/canvas/map/ND - top-level directory for all ND related files
  • $FG_ROOT/Nasal/canvas/map/ND/Boeing - for Boeing specific images/files (i.e. SVG artwork)
  • $FG_ROOT/Nasal/canvas/map/ND/Airbus - for Airbus specific images/files (i.e. SVG artwork)
  • $FG_ROOT/Nasal/canvas/map/ND/Citation - for Boeing specific images/files (i.e. SVG artwork)


It would make sense to integrate navdisplay.mfd with Richard's Canvas MFD Framework and consider using Emesary, too.

Team Boeing

Note  Please get in touch with the following contributors if you're planning on making changes related to the Boeing ND:
  • Gijs
  • Hyde


Team Airbus

Note  Please get in touch with the following contributors if you're planning on making changes related to the Airbus ND:
  • artix
  • omega95

Team Citation

Note  Please get in touch with the following contributors if you're planning on making changes related to the Citation ND:

Team CRJ

Note  Please get in touch with the following contributors if you're planning on making changes related to the CRJ ND:

A scratchpad regarding canvas based EFIS for Bombardier CRJ can be found here.

Backward Compatibility

Once we support a handful of different styles, it would probably make sense to refactor the styles file so that we end up with dedicated files for each style, e.g. by having:

  • $FG_ROOT/Nasal/canvas/map/ND/nd.airbus
  • $FG_ROOT/Nasal/canvas/map/ND/nd.boeing

However, it makes sense to wait a little with this so that multiple styles can evolve in parallel, to ensure that overlapping functionality can be generalized and refactored/reused. Equally, that would allow us to provide versioning support, so that aircraft developers can request a certain version of the "ND API", e.g.:

  • $FG_ROOT/Nasal/canvas/map/ND/nd.airbus-3_4
  • $FG_ROOT/Nasal/canvas/map/ND/nd.airbus-4_0
Cquote1.png Overall, we may sooner or later support backward compatibility by using something like io.include() with a nested version number and/or API call, it is not exactly difficult. For now, it would require just a few lines of code. The real challenge is that the code is very much evolving still, so it would be premature to implement something like that currently, especially because there's still many features missing so that establishing feature parity would be difficult at best - once things stabilize, and once multiple styles/aircraft are supported, we could/should revisit that. But unless someone else steps up to implement a working scheme, I am not currently interested in establishing such an API, at a time when things are being constantly refactored - we are seeing a handful of related efforts currently. And even Gijs mentioned a few times that he's still hoping to add new features - so adding API-level versioning/backward compatibility would probably become pretty awkward soon, and it would also involve other parts that don't actively support backward compatibility - Tom is using a getprop("/sim/version") check in api.nas - so it is possible, but only makes sense once the dust settles.

At least for the time being, I'd estimate that it might take another 2-3 release cycles until this should be re-considered, unless we'll see more ND contributions rolling in shortly. If you do want to work out some kind of BC scheme, you are obviously invited to post your thoughts, I won't object any suggestions and will surely help review them to get them committed - but I'd probably not target 3.0, but instead 3.2 so that you have some kind of baseline to work with.


— Hooray (Sat Oct 18). Re: What's going on with the Navdisplay.
(powered by Instant-Cquotes)
Cquote2.png

Getting rid of Aircraft Dependencies

  • the constructor/newMFD() methods still contain a few hard-coded assumptions due to the origins of the code, but those can be also moved into some kind of construct() field in the hash, or you can simply use the method I suggested, i.e. hiding unneeded symbols. But you're right, that the most proper solution would be identifying non-generic code that contains hard-coded assumptions and moving that into some kind of construct() field that is simply invoked by the new/newMFD() methods. Doing that would not be difficult, it would be just copy & paste - i.e. copy from the navdisplay.mfd file into the style file, by adding a corresponding hash field entry there - and calling it instead. Takes under 3 minutes[5]

Encapsulating Properties

Screenshot demonstrating how the Canvas-based ND was designed to use an encapsulated "data souce" abstraction to hide implementation details, so that the same ND code can be used for creating independent ND instances that support AI/MP (or Dual Control) aircraft for driving the MFD[8]. Note that this was prototyped in late 2013 originally[9], and may not have received much testing/attention lately.

Over time, the ND code has become kind of a mess - and we helped create/add to it,at a time when the ND code was not supporting multiple aircraft or multiple instances, so it evolved from then - my suggestion would be not to touch/refactor it anytime soon, it's really mostly procedural code that is including files with "configuration hashes" - e.g. navdisplay.styles and navdisplay.mfd if I remember correctly. The "mfd" file is a conventional Nasal file that sets up the whole thing, while the styles file is/was intended to keep aircraft specific stuff (think 777 vs 747 vs 737 vs. Airbus A320, 310 etc).

So we wanted to have some separation scheme in place, while providing a foundation for code reuse.

From a functionality standpoint, the meat of it are two for/each loops that either set up listeners for certain events (e.g. cockpit-specific switches), or just a generic timer that calls a bunch of callbacks in a vector - each callback there is treated as a predicate with a function that is evaluated, and the corresponding true/false callbacks being invoked accordingly.

It really is a rather simple/inelegant design, but it proved to be rather accessible for people without much of a programming background, i.e. the "styles" stuff can be touched/maintained and refactored by people without having to touch the other files. And that also applies to the *.mfd file

At some point, artix added a bunch of functionality to it - but originally we were hoping to even have manufacturer-specific style files with hashes - I just requested not to implement that back then, because I felt that the degree of code reuse taking place was still not satisfactory (think animation/event handling) - looking at Richard's MFD framework, the *.mfd file would be the right place to integrate it - and we would not need to touch the *.styles stuff at all to make that work.

The one thing were things may fall apart is that the ND code is intended to support independent instances, that may be driven by AI/MP properties, i.e. that may not even have a proper "aircraft/FDM/autopilot/route manager" associated, which means that the standalone UI use-case is/was intended to be supported.

Overall, it's a rather verbose, explicit and inelegant design, but there's a sane way to refactor things by just looking at navidsplay.mfd - the only feature implemented differently is mapping/charting, because that is using the MapStructure framework, which using fairly sophisticated metaprogramming, on top of a simple MVC framework, so that charting layers can be easily implemented/customized and maintained (think VOR, NDB, DME, AI/MP traffic, weather etc) - without any of those layers containing aircraft/use-case specific code, which makes the whole thing reusable elsewhere (e.g. see $FG_ROOT/gui/dialogs/map-canvas.xml).

In coding terms, there's a bunch of delegates used everywhere that are contained in various configuration hashes, so that the system can be easily re-configured, without having to use much/any copy & paste, apart from the configuration itself.


Note  The quotes below are mainly intended for people wanting to use the ND framework with other aircraft (e.g. those not driven by an actual FDM), and/or for visualizing situations for arbitrary AI/MP nodes in the tree (think bombable), but also dual control aircraft (multi-instance synchronization)
Cquote1.png think this and similar problems will be coming up for more or less every aircraft as the properties differ between FDM:s and sometimes even between aircraft.
Cquote2.png
Cquote1.png aircraft, which will use NavDisplay must have airspeed-indicator, created as built-in instrument, so I prefer to select airspeed indicator as source (which is close to reality).
Cquote2.png
Cquote1.png We could implement a way for individual aircraft to feed the ND with their own properties. However, standardizing the properties would make more sense, also for other use cases (bindings for input devices, dialogs etc.). That is even more important for the PFD, since there are dozens of different autopilot properties being used in FlightGear.
Cquote2.png
Cquote1.png This kind of thing is already done in both, MapStructure and the NavDisplay, it just isn't widely used yet - but we did establish the infrastructure for this. You must be aware of it, because you kept maintaining and fixing the NDSourceDriver logic that encapsulates properties for AI traffic. The same method can be found in aircraftpos.controller to hide aircraft specifics in a "delegate" hash that provides an "abstract interface" without the back-end code having to be aware of the underlying properties.


As long as aircraft developers agree to use, and help maintain, a corresponding hash, we can help enforce this as a "best practice" - but this should obviously be part of navdisplay.mfd itself (or even a separately-included file), so that it can be easily reused by PFD code eventually, without being specific to the MapStructure/ND code.

We reall only need to extend the ND.new() constructor to support such "overrides" and pass them on to the underlying callbacks, which is kinda touching the whole MVC approach already used by the MapStructure framework.


Cquote2.png
Cquote1.png Regarding the ND, it has something called a "driver hash" where all the position/orientation getters are turned into functions that can be overridden for arbitrary purposes - and that is how the ND code can be also used for showing NDs from the perspective of arbitrary AI/MP aircraft - I guess, you would want to take a look at that.

For testing purposes, you could use a standalone ND/map and configure the driver hash to use one of the dual/shared control aircraft in your MP property tree.

For some more background info, see: search.php?st=0&sk=t&sd=d&sr=posts&keywords=NDSourceDriver
— Hooray (Mar 26th, 2016). Re: Dual control for Boeing 777.
(powered by Instant-Cquotes)
Cquote2.png

Post 3.2

Gijs has already begun to clean up the update() method, and navdisplay.mfd is now back down to under 800 lines of code. Hooray will need to revisit adding support for switches (i.e. a helper class) and corresponding display modes.

At that point, we can probably simplify most things easily and move them out of the mfd file. There are some opportunities to clean up navdisplay.styles by looking at identical/similar code, such as the altArc vs. altArcCtr handling, which is basically boilerplate code. We can probably reduce navdisplay.mfd down to ~500 lines, which is mainly a matter of fixing up the update() method and making the newMFD() helper more generic.

Once that is the case, navdisplay.mfd should be generic enough to be also useful to help with porting the existing PFD code, because it's ultimately no longer ND specific, but simply a framework for animation SVG elements on a Canvas. Performance-wise, we can easily optimize the existing code by getting rid of the foreach() loop in update() and using exclusively listeners to selectively invoke only required predicates.

That would even allow us to get rid of the maketimer() hack. A few computations could/should probably be shared by being moved to common entry and writing their results to a hash, so that other elements can reuse previous results, e.g. altRangePx, rotation angles etc - there's no need, to recompute those in each callback. On the ctor side, we may want to allow individual elements to be easily disabled or overridden, analogous to how MapStructure layers can be customized/styled and disabled. We could probably reuse the same code we're using for options/style handling here. Otherwise, there are still a few "hard-coded" getprop() references that should better use the NDSourceDriver logic, or its MapStructure equivalent, i.e. aircraftpos.controller.

There are also still a few properties that should become configurable, like we once said WRT to having multiple instances of an instrument/radio etc

Cquote1.png just so that I don't forget about it: https://gitorious.org/fg/fgdata?p=fg:fgdata.git;a=commit;h=7da1406cfbed8af69c5cefe215b4f327d791423d


  • append(m.listeners ........ seems a common idiom: provide either a helper method or extend the underlying base class accordingly
  • WPT/RTE options should probably be exposed via the ctor, so that the ND code can override those defaults ?
  • unify WPT/RTE handling for wpt num ?
  • maybe provide a base class for both that encapsulates identical requirements ?
  • FIX.symbol still contains hard-coded colors, should be using styling there, TFC.symbol is using hard-coded colors & font size
  • navdisplay: swithcing between CTR & PLAN mode should be better supported by encapsulating setTranslation() etc
  • get rid of _draw_rwy_nd() calls
  • port runway-nd.draw , and support styling there

Cquote2.png

Misc

  • allow layers to be configured per aircraft ticket #1388
  • use switches hash to procedurally create a GUI dialog with buttons for each toggle_action (TFC,CTR,WXR etc) Not done Not done
  • make sure that the frameworks with properly with time-warp, sim-rate speed-up and replay Not done Not done
  • remove (old) disabled layers Done Done
  • use MapStructure options hash to encode things like nav/comm stuff (anything referencing /instrumentation/) Not done Not done
  • make the timer update interval configurable via constructor Not done Not done
  • consider adding hooks to generalize those huge conditionals in update() some more Not done Not done
  • use foreach() to hide()/show() or setVisible() groups of symbols Not done Not done
  • make this more aircraft agnostic by getting rid of 747/Boeing specific assumptions Not done Not done
  • unify switch/case|default handling (again, huge conditionals in there) Not done Not done
  • on_update() helper should probably support global listeners and timers, too - i.e. via a hash spec ? Not done Not done
  • the whole symbol lookup needs to cleaned up (getElementById etc) Not done Not done
  • introduce a common "compute" field in the update/predicate hash and make its results available to is_true/false etc Not done Not done
  • use io.include to include boeing specific stuffDone Done (see navdisplay.styles for now)
  • NDSourceDriver should be generalized and combined with MapStructure's aircraftpos.controller Not done Not done
  • the radio/autopilot listeners need to be set up in the lcontroller file, they're just empty stubs for now Not done Not done
  • there are 3 foreach loops in newMFD() setting up symbols currently-we only need ONE: loop 2+3 should be removed and use the loop #1 method Not done Not done
  • clean up the ctor and generalize the newMFD() method Not done Not done
  • move stuff out of the update() method into the aircraft-specific configuration hash Not done Not done
  • generalize/extend on_update() method to support other (global) properties and/or listeners/timers to run predicates Not done Not done
  • identify opportunities for improving the framework Not done Not done
  • support multiple routes (WPT/RTE), as per the Nasal Flightplan API (Hooray) Not done Not done
  • move the config hash out of the navdisplay.mfd file and use io.include instead Not done Not done
  • document SVG symbols currently assumed to be available in the ctor Not done Not done
  • consider using some of the SGCondition/StateMachine stuff in SG Not done Not done


Bugs

Feature Requests

  • feature layers: also pass the symbol instance to each predicate/callback to simplify those methods
  • generalize & unify with MapStructure layer handling (update, show, hide)

References

References