Howto:Work with the Property Tree API
Most FlightGear systems communicate internally using the so called Property Tree (see Property Tree Intro for an introduction).
This article is meant to introduce you to the C++ API that is used by FlightGear to work with the Property Tree.
Most of the high level API can be found in the $FG SRC/Main folder, specifically the files fg_props.cxx and the corresponding fg_props.hxx header are relevant (this is the one, that you'll want to include when working with the property tree).
The property tree implementation itself is to be found in the SimGear sources ([1] see specifically SGPropertyNode).
Note: Accessing property manager values via static propertynode pointers is more efficient because you only have to do the expensive string name hash lookup once the first time the routine runs.
1. Logically, the property system is a tree of labelled nodes rather than a lookup table. That means that if you get the value of /controls/flight/elevator, the property system starts at the root, looks up the first child named "controls" (in a list, since hashing gives no benefit for short lists), then looks up a child of the "controls" node named "flight", then looks up a child of the "flight" node named "elevator".
2. However, to speed things up, each node has an STL map attached for *caching* lookups, so when you lookup a node relative to another node, it will cache the result and get it from the map with a single lookup the next time.
3. Since every property value exists in a node, FlightGear subsystems often simply look up the node once and then manipulate it directly, just as Curt described. The node for /controls/flight/elevator will always be the same, even though the actual value changes. In YASim, Andy decided not to bother working through nodes and just goes straight through the lookup in #2, and I cannot see a speed difference against JSBSim. It wouldn't be hard to change YASim, though.
4. Note that all of the lookups can be relative to *any* node, so in the future, we can pass each subsystem a reference node at initialization time. For example, instead of running one copy of JSBSim lookup up properties in /, we could run three copies, one with the reference node "/vehicles[0]", one with the reference node "/vehicles[1]", and one with the reference node "/vehicles[2]". As long as the subsystems do relative property references (i.e. "controls/flight/elevator" instead of "/controls/flight/elevator", there should be no conflicts.
Modeling an API on top of the property tree
The property tree's purpose is to provide access to internal variables, i.e. by allowing people to inspect internal variables in a read-only fashion, but also in a writable fashion in many cases.
The property tree's support for "publishing" internal variables, that can be read from and written to using an intuitive symbolic name within a tree hierarchy, also makes it possible to register so called "listeners" which invoke internal code routines ("callbacks) whenever certain properties are accessed (read or modified).
This, in turn, makes it also possible to emulate a signal/slots mechanism (think Qt) on top of the property tree, i.e. by using "output properties" as signals, and "input properties" as "slots".
Routines that can be invoked this way include core C++ code, but also Nasal scripts.
More and more subsystems in FlightGear make use of this method to implement a subsystem-specific high level interface on top of the property tree.
There are certain advantages by using the property tree like this, because all FlightGear subsystems can easily access those "APIs" - i.e. once a certain feature is implemented and provided using the property tree, the corresponding features becomes accessible everywhere, it would no longer matter if you wanted to access the feature internally, e.g. using the property tree browser, using an XML configuration file, a Nasal script, a networking protocol or even the telnet/props interface - simply because all of these methods have access to the property tree. In other words, once a feature is implemented like this, RPC also becomes possible automatically. The AI traffic system is working like this for example: It registers a listener callback that gets invoked whenever updates take place for the corresponding branch in the property tree (such as /ai/models).
So, as soon as some part of FG makes modifications to this branch, the AI system is there to validate and process these modifications - i.e. in order to parse and see what it has to do.
At that point, it doesn't matter any longer if you are instantiating AI traffic manually (by using the property tree browser), using a Nasal script or a telnet connection into the property tree - simply because the AI system provides an API an top of the property tree that is accessible to all FG systems.
Another advantage is that there wouldn't be any changes required to Nasal or other existing code, this would be just about adding a new subsystem, making it listen to a dedicated branch in the property tree (such as /usb-hid) and processing events. This is where you'd need to map the core C++ API to property tree events, where you'd handle argument processing.
The same approach could be used to add other features to FG, without touching the Nasal code at all - such as socket support, or 2D rendering.
Checking for Property Existence
bool fgHasNode (const char * path);
Getters
SGPropertyNode * fgGetNode (const char * path, bool create); SGPropertyNode * fgGetNode (const char * path, int index, bool create);
Boolean Values
bool fgSetBool (const char * name, bool val); bool fgGetBool (const char * name, bool defaultValue);
Integers
bool fgSetInt (const char * name, int val); int fgGetInt (const char * name, int defaultValue);
Floats
bool fgSetFloat (const char * name, float val); float fgGetFloat (const char * name, float defaultValue);
Doubles
bool fgSetDouble (const char * name, double val); double fgGetDouble (const char * name, double defaultValue);
Longs
long fgGetLong (const char * name, long defaultValue); fgSetLong (const char * name, long val);
Strings
bool fgSetString (const char * name, const char * val); const char * fgGetString (const char * name, const char * defaultValue);
Using Listeners
Also see Howto:Creating new Subsystems.
void fgAddChangeListener (SGPropertyChangeListener * listener, const char * path); void fgAddChangeListener (SGPropertyChangeListener * listener, const char * path, int index);
Attributes
Persistance
void fgSetArchivable (const char * name, bool state);
Access
void fgSetReadable (const char * name, bool state); void fgSetWritable (const char * name, bool state);
Tied Properties
Note The origin of tied properties was largely to address
"hypothetical" performance concerns in the early days. Coders could continue to use native compiled variables and simultaneously expose them to the property system. Early property system skepticism included issues like performance and use of global structures. Those have largely not proven out to be actual issues. So without looking deep into how much effort/change/impact this would require, I think for the most part the whole tied property aspect of the api could go away and the property system api would simplify quite a bit.[1] |
Ideally, many more things would use the listener interface, and thus any polling of properties would be removed from all the other code, and only done if required by the property listeners themselves.
Please try to refrain from using tied properties. Tied properties have been known to cause issues and break other features of FlightGear. For additional information please see: Howto:Use Property Tree Objects.