Howto: Create new subsystems
From wiki.flightgear.org
| This article is a stub. You can help the wiki by expanding it.. |
| | Work in progress This article or section will be worked on in the upcoming hours or days. See history for the latest developments. |
This page is meant to document the necessary steps involved in setting up a new C++ subsystem in FlightGear. This is usually required in order to implement completely new components or features, but also in order to create new instrument implementations that are hardcoded in C++.
Also, implementing new subsystems in C++ is usually the preferred way of implementing computationally intense features or code that needs to be run at very high frequency, possibly even outside the main loop, code that may otherwise cause too much CPU load if for example implemented in scripting space.
On the other hand, many interesting features can already be implemented using Nasal support, and often it is also significantly easier for new developers to enhance the current scripting interface in order to make new APIs available to FlightGear, instead of coming up with a completely new C++ subsystem. The necessary steps to add additional APIs to the scripting system are documented at Howto:Extending Nasal.
In order to work through this tutorial, you will need to be familiar with:
- building FlightGear from source
- C++ programming
Note that as of 05/2009, none of the examples here have been tested to compile properly, your help in keeping this page updated is appreciated.
In general, to add a new subsystem you would have to create a derived class from the SimGear class SGSubsystem and define at least a small set of functions:
#include <subsystem_mgr.hxx> //required header file
class FGFX : public SGSubsystem
{
public:
FGFX ();
virtual ~FGFX ();
virtual void init ();
virtual void reinit ();
virtual void bind ();
virtual void unbind ();
virtual void update (double dt);
}
The init() functions should make sure everything is set and ready so the update() function can be run by the main loop. The reinit() function handles everything in case of a reset by the user.
The bind() and unbind() functions can be used to tie and untie properties.
Not all of these methods will necessarily need to be implemented in the beginning: a simple subsystem prototype might very well work by just implementing the update() method, and leaving the other methods with an empty function body.
For example, a very simple "dummy" subsystem might even implement all methods inline:
#include <subsystem_mgr.hxx> //required header file
class FGDummy : public SGSubsystem
{
private:
double m_delay;
void check() {
if (m_delay >= 5) {
SG_LOG(SG_GENERAL, SG_ALERT, "FGDummy says hi !\n");
m_delay=0;
}
}
public:
FGDummy ():m_delay(0) {}
virtual ~FGDummy () {}
virtual void init () {}
virtual void reinit () {}
virtual void bind () {}
virtual void unbind () {}
virtual void update (double dt) { delay+=dt; check(); }
}
After that you can register this class at the subsystem manager, which is implemented in $FG_SRC/Main/fg_init.cxx.
Specifically, you'll want to look for the fgInitSubsystems() function:
// This is the top level init routine which calls all the other
// initialization routines. If you are adding a subsystem to flight
// gear, its initialization call should located in this routine.
// Returns non-zero if a problem encountered.
bool fgInitSubsystems() {}
This is where you can another line reading:
globals->add_subsystem("dummy", new FGDummy);
Depending on your subsystems dependencies to other subsystems, you may want to place your subsystem before or after certain other subsystems.
Now the subsystem manager calls the update() function of this class every frame. dt is the time (in seconds) elapsed since the last call.
If you have a number of related subsystems that may inevitably be connected, you may want to checkout the class SGSubsystemGroup.
Using Property Listeners
If your subsystem is interested in specific property state, it can register so called property listeners using the SGPropertyChangeListener API, the interface of which needs to be implemented by your subsystem:
class SGPropertyChangeListener
{
public:
virtual ~SGPropertyChangeListener ();
virtual void valueChanged (SGPropertyNode * node);
virtual void childAdded (SGPropertyNode * parent, SGPropertyNode * child);
virtual void childRemoved (SGPropertyNode * parent, SGPropertyNode * child);
protected:
friend class SGPropertyNode;
virtual void register_property (SGPropertyNode * node);
virtual void unregister_property (SGPropertyNode * node);
private:
std::vector<SGPropertyNode *> _properties;
};
If you are just interested in being notified once a value is changed, you'll want to implement the valueChanged method, the childAdded and childRemoved methods will be invoked once subnodes are added or removed, this is for example useful if you are interested in all updates to a specific branch within the property tree.
So, if you know that you'll primarily have to know if a certain value is modified, you can concentrate on implementing just the valueChanged method.
In order to "subscribe" to certain properties, you'll want to make use of the register_property() and unregister_property() methods.
Referring to our earlier example, it can be basically enhanced like this (not yet tested or compiled):
#include <subsystem_mgr.hxx> //required header file
#include <props.hxx> // required for SGPropertyNode stuff
class FGDummy : public SGSubsystem, public SGPropertyChangeListener
{
private:
double m_delay;
SGPropertyNode* m_dummy_prop;
void check() {
if (m_delay >= 5) {
SG_LOG(SG_GENERAL, SG_ALERT, "FGDummy says hi !\n");
m_delay=0;
}
}
public:
FGDummy ():m_delay(0), m_dummy_prop(fgGetNode("/dummy/value"),1) {}
virtual ~FGDummy () {}
virtual void valueChanged (SGPropertyNode * node) {SG_LOG(SG_GENERAL,SG_ALERT,"FGDummy:Listener fired");}
virtual void init () {}
virtual void reinit () {}
virtual void bind () {}
virtual void unbind () {}
virtual void update (double dt) { delay+=dt; check(); }
}
