The FlightGear Main Loop

From FlightGear wiki
Jump to navigation Jump to search
This article is a stub. You can help the wiki by expanding it.


Background

Cquote1.png The bulk of the work that Flightgear does to simulate flight on your screen happens in a single thread on a single core of your processor. Some multithreading is possible but you tend to get one core running at 100% with other cores hardly doing anything by comparison.
— sanhozay (Jan 18th, 2016). Re: FlightGear not using all computer power.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png your whole notion of "processes" is already where you are mistaken, FG's main loop is really just a huge ugly nested for-loop that calls the ->update(double dt) loop of each SGSubsystem/Mgr instance (think fdm, audio, nasal etc) - but there are still a handful of pseudo-subsystems that don't even use this design, while others are working behind the scenes in background threads (mainly in simgear), or are interleaved with the FDM.
Cquote2.png
Cquote1.png You have a number of miscocentions concerning the FlightGear architecture, how subsystems are running there (sequentially), and how Nasal-based timers/listeners help make the main loop less deterministic. You will probably want to do a bit of reading.

In summary, the FlightGear main loop is at best using "cooperative" tasking, in the sense that each subsystem's update() method has to be invoked sequentially before the other subsystems are getting a chance to run. FlightGear itself is using multi-threading only a few places, i.e. in the form of "worker threads". Thus, anything running in the main loop also has the potential to contribute to blocking/delaying other subsystems.

In addition, there are special subsystems like the FDM, which are running FDM-coupled, i.e. interleaved, several times per timestep (typically 120 times)
— Hooray (Jan 5th, 2016). Re: interpolate() locks autopilot?.
(powered by Instant-Cquotes)
Cquote2.png

Multi-threading

Cquote1.png While the main loop is indeed single-threaded, there are a bunch of systems that do make use of additional cores, see: Howto:Activate multi core and multi GPU support

You can also run certain Nasal computations asynchronously using Nasal's "thread.newthread()" API: http://plausible.org/nasal/lib.html

However, care must be taken not to call ANY of the standard extension functions - i.e. ALL the FG APIs, including property tree access through getprop/setprop, props.nas etc.

The canvas itself is single-threaded currently, but we once talked about using OSG's multithreading support to update textures independently, and not just sequentially, while groups and nested canvases would still need to be updated in a serialied fashion, other groups and canvases could be updated in parallel.
— Hooray (May 25th, 2013). Re: Flightgear plotter updated..
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png I think the misconception is that he thinks our main loop is multi-threaded, while it is indeed entirely sequential. Like Philosopher said, each subsystem in FlightGear gets called to run its update() method, including the FDM, autopilot, scenery, terrain, sound, scripting, I/O etc - there's a fixed pre-determined order. Each stage gets to see the state as it was left over from the previous stage. It would require a fair bit of sophistication and internals knowledge to mess this up (and I can only really come up with two methods, without going into specifics). And like Philosopher says, you can explicitly design for this by using a "signal" property that signals "completeness".
— Hooray (Apr 13th, 2014). Re: Integration of Flight Illusion simgauges.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png the fgfs main loop itself is single-threaded primarily. While Nasal does provide support for threading, the majority of the FG-level interface is not thread-safe at all, including most extension functions and other subsystems. The majority of subsystems (i.e. those not using SGThread) are also single-threaded, which includes recent additions like Canvas.

The FDM itself is also a singleton by design while also being strictly single-threaded, with the fixed assumption that there's only ever a single FDM being used.

Subsystems are structured in a "cooperative multi-tasking (time sliced)" fashion, meaning that one subsystem lagging behind would slown down all the others accordingly - e.g. the Nasal garbage collector is being run in the main thread, which makes frame rate/spacing non-deterministic for the other subsystems that are not FDM-interleaved.

To learn more, see: [1] This is a link to the FlightGear forum.
— Hooray (Apr 2nd, 2015). Re: FG 64bit & Linux dependencies.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png FlightGear's main loop is primarily single-threaded, most of its subsystems were not added/maintained with thread-safety in mind.

Nasal is in fact the only component intended to be threadsafe - however, that does not apply to the FlightGear integration layer (extension functions/APIs).

Thus, you are in uncharted waters basically Which is to say that you generally want to avoid using threads and only consider them a tool for exploring alternate solutions.

However, worker threads can access arbitrary Nasal data structures using the threads module and its helpers for synchroniation.

So you would be "safe" to do background processing using worker threads, e.g. for calculations - as long as you don't access any FG specific extension functions, which includes timers, listeners etc
— Hooray (Dec 19th, 2015). Re: Space Shuttle.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png restrictions due to FlightGear's architecture, i.e. its main loop, the proeprty tree and extension APIs not being thread-safe
Cquote2.png

OSG Threading

Cquote1.png SG threading support is largely specific to offloading rendering-related computations to different threads, it will NOT directly help conventional FlightGear subsystems runing in the main loop, not without explicit coding - i.e. multi-threading or multi-processing and IPC.
— Hooray (Mar 15th, 2012). Re: [SUGGESTION] Multi-core FlightGear support.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png OSG threading is neat and dandy, but obviously it is only directly useful for rendering related paralleliation and related work. On the other hand, none of this comes for free. OSG's threading support really is impressive, but all those OSG threads must be kept busy. Properly. That's exactly the work that FlightGear is responsible for in its main loop.

OSG's multithreading support doesn't just come "automagically", just because FlightGear uses OSG.

The multithreading support in OSG must be explicitly supported by following certain OSG coding patterns and coding protocols. Otherwise, you won't benefit at all.

Obviously, OSG really is extremely powerful and also pretty good at parallelizing things, but you cannot necessarily see that when running FlightGear, but there is other OSG-based software which is making really good use of this.
— Hooray (Mar 15th, 2012). Re: [SUGGESTION] Multi-core FlightGear support.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png FlightGear itself doesn't make very much use of OSG's support for threading (background jobs), and like you've found out, certain flags need to be set to tell OSG to "try" to do things in parallel. But overall, OpenGL rendering (and thus OSG rendering) is perdominantly single-threaded. The only thing that can be done by applications like FlightGear is to use OSG infrastructure (coding patterns) that can internally be parallelied by OSG.

These OSG "coding patterns" are typically based on standard design patterns that are mapped onto OpenThreads to parallelize rendering related tasks.

Basically, as long as the proper coding patterns are used, OSG is smart enough to split up the workload for each frame and use different threads whenever possible and keep the GPU saturated.

Whenever that is not possible, everything will be done in the main thread, which means that latencies will become worse and framerate lower.
— Hooray (Sep 4th, 2014). Re: questions about performance with openscenegraph.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png Much of the code comprising the FlightGear rendering pipeline was ported from PLIB/SG and hasn't been updated in years - thus, isn't yet necessarily using the right/best OSG coding patterns or protocols - which is why applications like fgviewer (or osgviewer) will typically perform better than fgfs. In addition, there's a ton of code running in the FG main loop that is adding to mainloop slowness despite not belonging into the rendering/main thread, which is why a few core developers have been working towards moving non-rendering related code out of the main loop into worker threads, and eventually also into HLA federates. "fgviewer" is all about coming up with a lightweight viewer component that doesn't run any non-rendering related subsystems at all.
— Hooray (Sep 4th, 2014). Re: questions about performance with openscenegraph.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png I don't think we're currently really using any multi-threaded OSG rendering. Our property tree code isn't really thread safe, and neither are the corresponding extension functions.

It may be worthwhile to explore threading at some point, especially for canvases that don't have any external dependencies (i.e. no referenced canvas that would require serialiing).

For OSG it would then be friendlier to use a real scenegraph for each group/element, so that optimizations become possible.

Technically, we would need to use one dedicated SGPropertyNode for each canvas and use serialization at the property tree level. That would then allow us to run all updates asynchronously, and only serialize getprop() accesses to the main property tree. For example, each canvas would have its own property tree (SGPropertyNode_ptr), and we could have a single dedicated update thread that runs all updates.

For all "self-contained" canvases, that would mean that they could be updated in parallel - without stuff having to run in the main loop.

But for this to be worthwhile, we would need to adopt more OSG:: data structures to map Canvas elements/groups, e.g. onto osg::switch etc

This would require a bit of Nasal-level restructuring, to ensure that we use different APIs for accessing the canvas property tree (thread) and the main tree.

But overall this should be pretty doable thanks to the property tree-centric nature of canvas. We would just need to make sure that each canvas has its own tree.

If anybody is interested in exploring this, let me know, I can probably help a bit with this and come up with a basic prototype that is only serialized with the main loop to ensure that changes take effect.
— Hooray (Mar 1st, 2014). Re: extra500 - Avidyne Entegra 9 IFD - approach.
(powered by Instant-Cquotes)
Cquote2.png

Initialization

Adding a new subsystem

Cquote1.png conventional subsystem:
  • you need to link to the Python library (DLL/DSO), by changing the CMakeLists.txt file
  • you need to add a dedicated subsystem to the main loop
  • you need to initialie a python interpreter instance
  • you need to hook that up to different subsystems (think properties, GUI etc)
  • you need to expose SG/FG APIs to Python
Cquote2.png

Update

Reset/Re-init

Nasal

Listeners

Cquote1.png polling is only really recommended for properties that change per frame or really often. Otherwise, you will probably want to use listeners or less-frequent polling using a timer. the props.Node wrappers are slower than getprop/setprop because there's more Nasal-space overhead. Intuitively, the props.Node stuff should be faster, because of the cached reference - but the getprop()/setprop() code is native C code that uses a heavily optimied algorithm to look up properties (this may change once Tom ported the props bindings to use his cppbind framework).
— Hooray (May 19th, 2013). Re: Reading properties.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png a listener would be quite appropriate for an on/off switch or similar, an update loop for everything that needs to be updated each frame/update (i.e. engine rpm), and if something is changing every frame anyways or is a tied property, you might as well use the update loop for several reasons: besides the fact that it might be necessary, it linearies your data flow and moves it closer to the use of the data, it makes use of the current known efficiency of getprop(), and I would venture that it seems most familiar to you. I really wouldn't worry about listener efficiency here, but then again what do I know (I have very little experience in real-time systems).
— Philosopher (May 19th, 2013). Re: Reading properties.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png t's NOT a "background process" at all - it will be triggered by the property tree once a node is accessed, which will invoke the Nasal callback. Timers and listeners are NOT background "processes". They are just invoked by different subsystems, i.e. the property tree (listeners) or the events subsystem (timers). There are other subsystems that can also invoke Nasal handlers, such as the GUI system or the AI code. This all takes place inside the FG main loop (=main thread), not some background/worker thread. Which is also the reason why all the Nasal APIs are safe to be used.
— Hooray (May 19th, 2013). Re: Reading properties.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png that's a misconception related to listeners: listeners are not actively "listening" at all - there's no "listener watch" running - instead, it works basically like this:
  • register a listener for some property named "foo", to call some Nasal code
  • which just adds a callback to a property specific vector, NOTHING else.
  • once setprop("foo", value) is called
  • the property tree is updated
  • next the property tree checks if any listeners are registered for that branch
  • if there are listeners attached, they are all called (there's basically a vector of listeners)


So listeners are not really "processes" at all - neither background nor foreground: Merely their *callbacks* become processes after being invoked. Otherwise, they're just a vector of Nasal callbacks - which are only ever called if the property is modified. In other words, there's basicallyero cost. Listener overhead is mainly determined by the callback's payload - not by listening (which is just checking vector.size() != 0 and calling each element), unless the property is updated frequently (in terms of frame rate) Admittedly, having many callbacks/listeners attached, could also add up quickly. I hope that clarifies things a little - it would be appreciated if you could add your findings of the whole thread and add them to the wiki :D

Started here: Using listeners and signals with Nasal#Behind the Scenes of Listeners
— Hooray (May 19th, 2013). Re: Reading properties.
(powered by Instant-Cquotes)
Cquote2.png

Timers

Cquote1.png In a sense, timers are "active" - while listeners are "passive", because they get only ever invoked once the property is modified. Still, the reason for using timers is usually saving resources - by running certain code at configurable intervals.

In other words, there are subsystems that run Nasal code, without showing up in the Nasal category - such as property tree or events system.

Still, that doesn't currently tell you which code (function, loop, file, line number etc) is really adding up/expensive.
— Hooray (May 20th, 2013). Re: Reading properties.
(powered by Instant-Cquotes)
Cquote2.png