Jump to: navigation, search

Canvas Development

7,336 bytes added, 21 February
Challenge: Multithreading:
It is trivial to run Nasal in another thread, and even to thread out algorithms using Nasal.
Nasal itself was designed with thread-safety in mind, by an enormously talented software engineer with a massive track record doing this kind of thing (background in embedded engineering at the time).
FlightGear however was never "designed" like Thorsten alluded to, rather its architecture "happened" by dozens of people over the course of almost 2 decades meanwhile.
The bottleneck when it comes to threading in Nasal is indeed FlightGear, the very instant you access any non-native Nasal APIs, i.e. anything that is FlightGear specific (property tree, extension functions, fgcommands, canvas) - the whole thing is no longer easy to make work correctly, without re-architecting the corresponding component (think Canvas).
In the case of Canvas, it would be relatively straight-forward to do just that, by introducing a new canvas mode, where each canvas (texture) gets its own private property tree node (SGPropertyNode) that is part of simgear::canvas, at that point, you can also add a dedicated FGNasalSys instance to each canvas texture (Nasal interpreter), and that could be threaded out using either Nasal's threading support or using simgear's SGThread API.
Obviously, there would remain synchronization points, where this "canvas process" (thread) would fetch data from FlightGear (properties) and also send back its output to FlightGear (aka the final texture).
Other than that, it really is surprisingly straightforward to come up with a thread-safe version of the Canvas system by making these two major changes - the FGNasalSys interpreter would then no longer have access to the global namespace or any of the standard extension functions, it could only manipulate its own canvas property tree - all I/O between the canvas texture thread (Nasal) and the main loop (thread) would have to take place using a well defined I/O mechanism, in its simplest form a simple network protocol (even telnet/props or Torsten's AJAX/mongoose layer would work "as is") - more likely, this would evolve into something like Richard's Emesary system.<ref></ref>
[...]there is a thing called the global property tree, and that there is a single global scripting interpreter. The bottleneck when it comes to Nasal and Canvas is unnecessary, because the property tree merely serves as an encapsulation mechanism, i.e. strictly speaking, we're abusing the FlightGear property tree to use listeners that are mapped to events, which in turn are mapped to lower-level OSG/OpenGL calls - which is to say, this bottleneck would not exist, if a different property tree instance were used (per Canvas (texture)).
This, in turn, is easy to change - because during the creation of each canvas, the global property tree _root is set, which could also be a private tree instead.
Quite literally, this means changing 5 lines of C++ code to use an instance-specific SGPropertyNode_ptr instead of the global one.
At that point, you have a canvas that is inaccessible from the main thread (which sounds dumb, but once you think about it, that's exactly the point).
So, the next step is to provide this canvas instance with a way to access its property tree, which boils down to adding a FGNasalSys instance to each Canvas - that way, each canvas texture would get its own instance of SGPropertyNode + FGNasalSys
Anybody who's ever done any avionics coding will quickly realize that you still need a way to fetch properties from the main loop (think /fdm, /position, /orientation) but that's really easy to do using the existing infrastructure, you could really use any of the existing I/O protocols (think Torsten's ajax stuff), and you'd end up with Nasal/Canvas running outside the main loop.
The final step is obviously making the updated texture available to the main loop, but other than that, it's much easier to fix up the current infrastructure than fixing up all the legacy code ...
telling the canvas system to use another property tree (SGPropertyNode instance) is really straightforward - but at that point, it's no longer accessible to the rest of the sim.
You can easily try it for yourself, and just add a "text" element to that private canvas.
The interesting part is making that show up again (i.e. via placements).
Once you are able to tell a placement to use such a private property tree, you can use synchronize access by using a separate thread for each canvas texture (property tree).
But again, it would be a static property tree until you provide /some/ access to it - so that it can be modified at runtime, and given what we have already, hooking up FGNasalSys is the most convenient method. But all of the canvas bindings/APIs we have already would need to be reviewed to get rid of the hard-coded assumption that there is only a single canvas tree in use.
Like you said, changing fgfs to operate on a hidden/private property tree is the easy part, interacting with that property tree is the interesting part.
Also, it would be a very different way of coding, we would need to use some kind of dedicated scheduling mechanism, or such background threads might "busy wait" unnecessarily.<ref></ref>
providing a new execution model for new Canvas modules where a Canvas texture has a private property tree that can only be updated by a Nasal script that runs outside the main loop would be feasible, and is in line with ideas previously discussed on the developers mailing list - furthermore, that approach is also in line with the way web browsers have come to address the long-standing issue of JavaScript blocking tabs, by coming up with the "web extension" framework, using a message-passing based approach - with one script context running outside the main thread ("background scripts"=, and another one ("content scripts") running inside the main loop communicating only via "events" (messages).
This kind of setup could be made to work by providing a new/alternate Canvas mode, where the Canvas-tree would never show up in the global tree, but instead bound to a private FGNasalSys instance, minus all the global extension functions.
With the exception of nested canvases, i.e. those referencing another canvas via a raster image lookup - those canvas textures could be updated/re-drawn outside the main loop, and would only require a few well-defined synchronization points, i.e. those fetching updated properties/navaid info, and providing the final texture to the main loop, and this is where Emesary could become a real asset.
In and of itself, this won't help with legacy aircraft/code - at least not directly, but it would provide an alternative that people interested in better performance could adopt over time, while investigating how legacy code could be dealt with, so that it can benefit without too much manual work (such as providing a list of subscribed properties, that are automatically copied to the private property tree running in the background context) - this won't be as efficient, but having a list of input/output properties could work well enough for most people's code<ref></ref>
=== Use Cases ===

Navigation menu