Recommended Property Tree Enhancements
This article or section contains out-of-date information
Please help improve this article by updating it. There may be additional information on the talk page. |
Intro
While FlightGear's powerful PropertyTree implementation can be considered to be the core of FlightGear's dynamic, flexible and open nature (and thus also FlightGear's success in the first place) it is more and more often getting obvious that the PropertyTree itself could benefit from an -at least optionally- more structural approach to processing (reading/writing) certain data (property tree variables), whose integrity may be directly critical for the simulator's integrity itself. This applies in particular in an increasingly parallel, multi-threaded and possibly distributed environment. Where data flow dependencies and relationships need to be made as explicit and obvious as possible.
Today's Practice
Simply put: property tree use in many places in FlightGear today is often following a very simple pattern, where the property tree is simply used as a (very) powerful and easy-to-use "dump space" for all sorts of data and runtime state stored in property tree variables, most of which often being completely typeless or at least very losely-typed, so that access to this state is at most also very losely-organized due to the lack of integrity-enforcing and access-limiting mechanisms.
While this flexibility can be tremendously appreciated from a user's point of view (because it makes it so much more intuitive and easy to use the property tree) this very flexibility (that is, the lack of typing-requirements and data-integrity enforcement mechanisms) can be considered to be a troublesome factor when it comes to ensuring (and optionally also enforcing) proper internal sim state to guarantee valid state for critical variables that may seriously impact simulator performance.
Thus, data flow dependencies need to be formalized and made explicit [1] in order to help FlightGear become more structured in its housekeeping department. Implementing these suggestions will automatically also help in other areas that are currently limited by FlightGear's current model of internal state management, such as for example by making it easier to implement support for dynamically switchable aircraft (for details, see FlightGear Sessions or Reset & re-init).
In addition, this very meta information will also help make FlightGear more easily parallelizable:
"In general, FG has quite a few data dependencies internally which make multi-threading challenging right now - there's groundwork to make the data-dependencies more explicit (i.e, via the property tree) that has to happen before pieces can easily move to other threads."[2]
Problems
No access control taking place
Currently, it is possible -and common practice- for all properties to be easily accessed (read) and written to from arbitrary FlightGear subsystems and components. In fact, it is even possible for aircraft configuration files and scripts to affect/overwrite crucial internal state.
Encapsulation is basically non-existent for many properties. This is a theoretical nightmare from an data integrity point of view, because state may be mutated from places and by components that may -strictly spoken- have no business exercising write access for certain properties or even whole subtrees of the property tree.
In addition, such losely structured and organized write access to properties also raises the question of housekeeping and cleanup responsibility, once data needs to be cleaned up for example, i.e. when re-initializing a specific subsystem or possibly the whole simulator.
As of 05/2009 this is an issue that has also been discussed on the jsbsim developers mailing list, because there is currently no clear policy whether distinct components (such as an FDM) should generally only mutate internal/private state (i.e. state that is at least conceptually 'owned' by the component) or whether components should also be allowed to mutate outside state outside their own branch of the private property tree (see Constraining Property Tree Access for FDMs for details).
No concept of (exclusive) property ownership
In fact, it is unfortunately even possible to easily register multiple writing property listeners for the very same property [3] (for example, it would be possible -and has been the case in the past- that registered listeners are exercising write access to properties exclusively owned (at least conceptually) by other components, such as the FDM, whose state would then be automatically invalidated due to its registered listeners each time a property is updated).
So that, one change (write access) to such a property may result in multiple subsequent write accesses to the same property by its registered listeners.
This is problematic in that property tree state may be mutated and invalidated by its own registered listeners [4]. And for the majority of FlightGear components relying on tied property listeners, this would indeed result in undefined behavior and can be considered troublesome, because conceptually components such as an FDM engine will normally require exclusive write access to certain output properties, i.e. it will require the right to full owernship.
Concerning the Concept of Property Ownership
It's pretty obvious that this might probably also be very useful to help prepare FlightGear for a more modular and parallelized (multithreaded) future (as discussed and proposed in [5]), where it would be of paramount importance to provide very finely-grained access to data that may possibly require locking in a threaded environment, so that FG components would no longer directly write to properties (that are conceptually owned by other subsystems) but rather write their requests [=new values] to a blocking, component-specific queue, which would in turn be processed (validated, checked for integrity) by the owning component in a sequential fashion to ensure valid internal state.
Basically, resembling/emulating queue-based Software Transactional Memory. FlightGear components would then publish a public getter method which could be accessed by all other components to read arbitrary component-specific state, unlike updates (write access) to properties, which would only take place using a well-defined component-specific interface.
To enforce property ownership, component-specific properties could be generally maintained in a dedicated SGPropertyNode container for each component, so that components (i.e. currently SGSubsystems) would feature their own private property tree, rather than following the current approach of having one global dump space for all properties (which is understandably hard to provide properly synchronized access for)
That way, subsystems (or components) could automatically "mirror" all private component-specific properties within the global property space (for easy accessing), however write access could easily be made totally optional, and could be tunneled through a synchronized, component-specific queue that contains all update requests. It can be assumed that implementing such or similar mechanisms to improve data encapsulation would help improve and modularize the overall architecture.
From a usability point of view, it would also still be possible to provide abstract wrappers in the form of the fgGet*/Set* utility functions that internally rewire transactions properly.
In addition, subsystems that provide and maintain their own private property tree, can be easily run in different threads or even as different processes without requiring thread-level sychronization (locks, mutexes/semaphores)
To quote [6]:"This is in fact in line with the approach David Megginson (designer and developer of the property tree implementation in FlightGear) proposed in a discussion about possible ways to prepare FlightGear for a multi-threaded future: subsystems would all have their own instances of a property tree so that reading/writing (subsystem-specific) values could happen in an organized fashion where each read/write request is dispatched to the corresponding subsystem in order to ensure that these requests are happening in a coordinated fashion. Still, each subsystem could publish "pointers" (or aliases) to a global property tree which would be accessible to all other subsystems, yet accesses would be dispatched so that subsystems could be arbitrarily threaded because each subsystem would handle its own property tree internally. Thus, the locking overhead would also be extremely minimized because the property tree would be partitioned into subsystem-specific toplevel nodes, where access to anyone property doesn't necessarily require other nodes to be inaccessible/locked (see [7] and [8]"
Differentiating between "active" and "passive" listeners
While there are thinkable scenarios where such a behavior is desired, i.e. to implement software filters, the approach of coming up with new state by overwriting the underlying original state can be generally considered bad practice as it could be much more reliably, powerfully and easily implemented by adding a corresponding layer of indirection, where new output doesn't automatically invalidate original input state, which then wouldn't be available any more.
In fact, at least informally on the FlightGear Devel mailing list, this problem has been repeatedly discussed, so that it is now generally considered bad practice to register multiple updating/writing listeners for the same property.
However, on the other hand there is currently no clear distinction taking place between active and passive property listeners.
Basically, for more controlled access to properties, there needs to be a formal difference between listeners that require write access to a property and non-writing listeners that must not affect the state of the listened-to property. By providing the corresponding wrappers around the SGPropertyListener interface (i.e. SGPassivePropertyListener(const&) & SGActivePropertyListener) it should be possible to establish a more formal approach to accessing properties via listeners within the FlightGear core code.
No formal validation taking place
Today, in FlightGear's core code there are numerous places where initial key-state is -for the sake of flexibility- retrieved dynamically from the property tree. Which is a good thing.
However, only rarely proper and full validation of the obtained data is done afterwards, rather unvalidated data is often directly used in statements that may at some point either affect the simulator's performance adversely, or even completely shut down the simulator.
Examples for possible problems:
- values of wrong type being written to/read from a node
- values of wrong unit being written to/read from a node
- values outside of valid range being written to/read from a node
- "0" values read from nodes that must not be 0
- type of a property changed, that shouldn't be changed
- crucial runtime constants being changed at runtime
- mis-spelled property names [9] - this could be easily addressed by extending the current fgSet*/fgGet* API functions to also take an optional parameter indicating whether a node MUST_EXIST, NOT_NULL etc.
...
Using automatic validation
While the lack of proper validation is probably mainly because of the tedious process of manually validating data every time something is read from the property tree, this "necessary evil" could be greatly simplified by enriching the property tree itself with attributive meta information for critical nodes/variables, so that the property tree itself could -given the availability of said meta information- handle taking care of ensuring data integrity by doing optional background validation (i.e. possibly SGPropertyListener-based) when writing new state to such critical nodes. Likewise, variables whose state would be monitored and managed this way, would automatically guarantee valid state, so that manual validation would become obsolete.
Introducing "Managed Properties"
Making the property tree thread-safe is an interesting but a very daunting task since properties are (ab)used in so many ways.. :) I have a soft spot for using multi-buffering to support concurrent readers with concurrent writers, but for something as unstructured (in terms of who writes where) as this I'm at a loss to see how or if it could be applied. — Anders Gidenstam (Jan 26th, 2016). Re: [Flightgear-devel] Designing a thread-safe property tree API
(was Re: A FGPythonSys implementation: ...).
(powered by Instant-Cquotes) |
the property tree as it is currently is in need of some rework because of the ownship (single desktop aircraft) approach. This is easier than it sounds - basically most of the property tree becomes part of the aircraft and only a few items are shared. This will also allow the switching of aircraft. The reason to consider this now, and maybe not implement it, is to ensure that the design will support this when it is time to implement it. — Richard Harrison (Nov 19th, 2015). Re: [Flightgear-devel] HLA developments.
(powered by Instant-Cquotes) |
The idea is to introduce so called "managed properties" for crucial runtime state, in order to optionally ensure, enforce and maintain integrity of such state variables by using additional meta information to apply data-integrity requirements, as well as access privileges to prevent important runtime state from being mutated by non-authorized components or invalid data.
Recommended new 'types' for nodes
Currently, there is no clear distinction taking place between arbitrary strings, filenames, paths, property paths or property nodes - this makes it increasingly complicated to keep track of what sort of data is represented by a particular node/string in the tree and makes it basically impossible to provide validation routines, or even come up with XML schemas [10] that might help validating PropertyList-encoded XML files.
Thus, it is suggested to start differentiating clearly between these different types of strings:
- type="string:path" - value to be specified is a path
- type="string:filename" - value to be specified is a filename, this would help tremendously in tracking file dependencies among FlightGear base package XML files (see [11]).
- type="string:property-path" - value to be specified is a property path
- type="string:property-node" - value to be specified is a property node
Suggested new attributes in PropertyList XML files
- "scope" or "lifetime" to formally describe the scope of a node as discussed in FlightGear Sessions
- "live" (or similar, jsbsim these properties are called "interface properties" [12]) - to indicate dynamically updated (at runtime) node data, i.e. automatically register a corresponding listener for such properties, rather than just using the static value obtained during parse time and treat it as const then, this is likely to be useful for a number of scenarios: right now, each individual FG component requiring this feature, needs to implement it separately, i.e. the autopilot code now features support for dynamically modifiable properties, as well [13], the same applies to the gui/dialog code, which may also make use of "live" properties [14], whose values may change at runtime. Increasingly, it is getting obvious that other FlightGear components may also benefit from such a feature [15] or [16], [17]. Thus, it would seem appropriate to generalize the concept of "live properties", whose values may be changed and ought to take effect accordingly. This attribute might come from another attribute to indicate the corresponding listener-bound property, i.e.:
<PropertyList> <foo type="double" live="true" property="/foo/myfoo/value"/> </PropertyList>
- "default-value" - to enable contributors to provide sensible default values in XML files (jsbsim has this already in the form of an initial value [18], [19])
- "value" attribute - to directly provide node values as attributes more easily in the form of "<node type="string" value="foo"/> instead of the currently required form "<node type="string">foo</node>"
- "description" attribute - to directly document the purpose of a node, so that documentation can be automatically created by walking the property tree
(validation optional: runtime-configurable)
- "unit" to directly keep track of the unit of a numeric value and optionally ensure that only values of same unit are written to a variable (currently, units are merely by convention tracked using a corresponding suffix) (also see: [20])
- "min"/"max" attributes to define valid ranges for numeric values [21]
- "stepping" attribute to define valid stepping for numeric values
- "enum" for textual multi-state variables, to ensure that only valid state can be written to such a variable
- "enum-values" - store valid values for enum type
- "wrap/mod" - to specify whether a value overflow means to flip back to the 1st valid state [22]
- "notnil" - value may never be empty/unset
- tie restrictions ? [23]
- listener restrictions (max/none)?
- max simultaneously registered active (writing) listeners
While many variables in FlightGear can currently be easily changed at runtime, their changes do not necessarily take effect immediatley, i.e. because there are no listeners bound to them, or because the corresponding subsystems do not yet support re-initialization to work with an updated value. In fact, there are several variables that cannot take effect without resetting individual subsystems, resetting the sim or even completely restarting FlightGear.
It should be considered paramount to document the mutability (or lack thereof) of such variables to ensure that the core's runtime system may eventually become able to track down (and warn about) situations where users are trying to write new state to such variables who may not be aware of these restrictions.
This could probably be achieved by providing attributes for the corresponding nodes that explicitly highlight restrictions such as:
- changes may not take effect until subsystem reset
- changes may not take effect until session reset
- changes may not take effect until simulator reset
- changes may not take effect until complete simulator restart
Additional information may need to be provided in order to provide further information about which subsystem/s need to be reset, this could be based on global subsystem inode IDs in the form of 2/4/8/16/32... to enable arbitrary combinations of subsystem dependencies.
"readonly/constant" for constant values that may not change during runtime (or currently: whose changes won't take effect until restart?)[24] Done (by TheTom [25] [26])
- "filetype:" to explicitly highlight type of file being expected/provided (i.e. "texture/rgb") - this should probably make use of standard MIME types
- "default-working-dir" - default cwd if only filename specified without path info
Introducing Variables in PropertyList XML Files
- introducing the concept of "base/frame pointers" for property nodes (and possibly attributes) as described in [27] would surely simplify PropertyList XML files tremendously and make them much more intuitive to work with (this is in fact conceptually pretty much identical to another suggestion, to provide basically the same facility for (non-relative, file system) paths and filenames provided in aircraft/PropertyList XML files, as posted in response[28] to a request by C. Olson to enable aircraft to become more easily relocatable [29] [30]). This could for example be achieved by generalizing the current concept of aliases or by introducing a possibility to set file-specific (or possibly just global) variables in XML files that can later on be referenced and resolved in attributes or nodes in the XML file, i.e. along the lines of (the details might still need to be discussed, though):
<PropertyList> <!-- setting up some variables to be used in attributes or nodes --> <xml-variable name="ROOT_TREE"></xml-variable> <xml-variable name="FG_ROOT"></xml-variable> <!-- preferably, the underlying code would feature support for recursion to allow for use such as: --> <xml-variable name="SIM">ROOT_TREE/sim<xml-variable> <xml-variable name="FG_AIRCRAFT">FG_ROOT/Aircraft</xml-variable> <!-- making use of "base pointers" to directly refer to the proper location instead of using lots of nested relative path specs --> <aircraft-filename type="string">[[$FG_ROOT]]/myAircraft/myAc-set.xml</aircraft-filename> <some-property type="string">$ROOT_TREE/gui/ </PropertyList>
Implementation-wise, it would be possible to add support for this by either extending the current XMLVisitor code in simgear, or by sub-classing it, so that a static std::map<std::string,std::string> could be used to store variable/value pairs, and automatically parse each encountered attribute or node value for a corresponding variable identifier token that should normally not show up in common use scenarios (such as i.e. '%'). That way, all such occurrences could be automatically looked up in the std::map and resolved to their corresponding equivalents at runtime. So that users would then be able to easily refer to "short hands" for well-defined locations (it might make sense to differentiate between local and global xml variables, the latter of which could automatically take effect for all parsed XML files, while the former would only use a lookup map for the root XML file or any files it references using the include directive).