20,741
edits
m (→Supported Layers: forum2wiki) |
|||
| Line 87: | Line 87: | ||
* multiplayer traffic {{Progressbar|80}} (by Gijs) | * multiplayer traffic {{Progressbar|80}} (by Gijs) | ||
* AI traffic {{Progressbar|80}} (by Gijs) | * AI traffic {{Progressbar|80}} (by Gijs) | ||
See the forum for details: http://forum.flightgear.org/viewtopic.php?f=71&t=21139 | |||
== ToDo == | |||
Originally, the idea was that we want to avoid the ongoing degree of copy/paste programming that could be seen in the airport selection dialog - next, the 744 ND duplicates lots of code used in the airport dialog, but also in the underlying framework - all without using inheritance or delegates/predicates to customize things. So over time, we would end up with tons of custom code that is highly specific to each aircraft/display and GUI dialog. | |||
From a low-level point of view, it shouldn't matter to the draw routines if they're called by an aircraft instrument or by a GUI dialog. However, once they contain use-case specific logic, such as getprop() calls and other conditionals, things do get messy pretty quickly. Thus, what I have been doing is simply copying things to separate *.draw files for different scenarios, i.e. I now ended up with airport.draw and airports-nd.draw for example - that isn't all that elegant, but at least it solves the problem of having to implement full models and controllers for each scenario (well for now), because all the embedded logic can stay, and only needs to be refactored later on. | |||
Thus, the idea is basically this: | |||
*.draw files contain the low-level logic to draw arbitrary symbols (runway, waypoint, taxiways, airports, traffic) - without knowing anything about the frontend/user - so that such details needs to be separately encapsulated. The *.model files merely determine what is to be drawn, data-wise, as in NasalPositioned queries and their results - all stored in a single Nasal vector. The layer files turn everything into a dedicated canvas group that can be separately toggled/redrawn/updated - the currently missing bits are controllers that tie everything together, so that each frontend (instrument, MFD, GUI dialog, widget) can separately specify and customize behavior by registering their own callbacks. | |||
Most of this is already working here (with very minor regressions so far that Gijs won't be too happy about ...), but otherwise I managed to turn Gijs code into layers/models that can be directly used by the old framework, i.e. I can now add a handful of new ND layers (fixes, vor, MP or AI traffic, route etc) to the airport selection dialog (or any other dialog actually), and things do show up properly. | |||
What's still missing is the controller part, and styling for different aircraft/GUI purposes - also, Gijs' ND routines are currently used directly, without any LOD applied - but I think that can be easily solved by using the canvas's scale() method. | |||
Just to clarify: stuff like getprop("instrumentation/nav[0]/frequencies/selected-mhz") doesn't belong into the visualization part of the code (i.e. the draw/view stuff) - simply because that's the sort of stuff that would "break" once we're using a different use-case, e.g. the map dialog - which is such conditions would need to be either specified in some form of condition hash, as part of the draw* signature, or via a dedicated Drawable class that's able to evaluate such conditions. | |||
To be honest, that was the whole point of moving stuff to different *.layer and *.model files - so that these *.draw files can be shared, without requiring any modifications - your current example would still require using differrent *.draw files for each purpose. | |||
* Implementing proper LOD support for *.draw callbacks (i.e. using canvas/scaling) | |||
* Implementing support for different styles | |||
* Supporting multiple NDs per aircraft | |||
* Supporting multiple maps per dialog | |||
* Stress Test: render a dialog with 10 NDs, for 10 different MP/AI aircraft | |||
* clean up map.nas, get rid of stubs and old code | |||
* get rid of the PUI helpers in map.nas, and use real canvas dialogs instead | |||
* Optimize (Nasal/Canvas and C++ space) | |||
== Styling == | |||
Supporting styling is one thing that we missed in the first iteration - but obviously we need a way to allow end-users to customize their ND, by either providing a custom draw callback, by specifying a SVG files, or by implement a "Drawable" interface. | |||
And looking at Gijs 744 code, we also need a way to "inject" custom logic to style/mark elements, depending in controller-specific conditions, such as the range selected in the cockpit, or in the GUI dialog - or the next waypoint. | |||
== Optimization == | |||
From an optimization point of view, having to call the .removeAllChildren(); method in each draw routine is probably not strictly necessary, given the sophisticated spatial queries that are now supported by NasalPositioned: if we could look up canvas groups/elements by a key (say VOR/FIX/DME identifier), we could only remove those that are actually out of range - I think the property tree code could be easily modified accordingly, and the NasalPositioned APIs could then be told to return the corresponding subset of "new" geographic positions, instead of having to redundant work. | |||
Overall, we can work with the assumption that the aircraft is usually moving, so permanently updating its center/origin coordinates to selectively prune entries that are no longer in range should be more efficient than doing the whole thing in scripting space. | |||
So there are probably some features that can be used to identify missing/useful extensions, not necessarily as dedicated canvas::elements, but rather as extensions to existing elements - once we have a more final version, we can do some profiling to find the low-hanging fruits. | |||
Regarding spatial queries, on the nav-cache side, delta queries would be complex to support. What the C++ NavDisplay does is keep a persistent list which is updated infrequently - only when a setting changes (range, view options config), or when the aircraft moves > 1nm. In C++, computing the delta of two arrays is fast, and that's what I would suggest to avoid the 'remove all children' logic, which is obviously unfriendly to the Canvas. What I'm not sure about, is if Nasal currently has a nice API to compute the difference (added / removed items) between two arrays, but if it's missing I am sure it can be added. | |||
I don't think we have any real Nasal API for getting a delta of two vectors - but I think, we'll want to avoid Nasal-space overhead here (aka a foreach loop), and simply add a corresponding C/C++ extension function, that directly operates on pointers/NasalPositionedGhosts, and then just return the corresponding subset. | |||
On the canvas side, it would obviously be useful to address elements/groups by ID rather than index, because we could then selectively remove entries that are no longer valid. But maybe Tom has a better idea ? | |||
the whole removeAllChildren() method is unfriendly to the canvas, and should eventually be replaced with something more efficient. | |||
Which is why I would prefer to encapsulate it in some form of helper method, which can later on be re-implemented by touching only a single place. | |||
At the moment, these update() methods are all structured in the same fashion, i.e. calling removeAllChildren() first, and then proceeding with the redrawing stuff - I would prefer to introduce a helper method here, something like having a refresh() method that would hide the removeAllChildren() internals, which could later on by re-implemented by calling a dedicated Nasal extension function to determine the delta between two vectors (like Zakalawe mentioned) - basically something like: | |||
<syntaxhighlight lang="nasal"> | |||
var vec1 = [1,2,3]; | |||
var vec2 = [0,1,2,4,5]; | |||
# return a 2-element vector with removed/added elements | |||
var delta = vec_diff(vec1, vec2) | |||
var removed = delta[0]; # removed now contains all elements that are no longer in the 2nd vector | |||
var added = delta[1]; # contains all elements that are new, i.e. not in the first vector | |||
</syntaxhighlight> | |||
Which would allow us to look up removed elements/groups, and only remove those from the canvas subtree, while just adding new ones as required. | |||
This could be added as a simple extension function, or to optimize things even further, as dedicated NasalPositioned methods, or possibly even as Nasal Ghosts that operate on Canvas pointers directly. | |||
== Full Example: Creating dialogs with embedded Canvas Maps == | == Full Example: Creating dialogs with embedded Canvas Maps == | ||