Howto:Add new fgcommands to FlightGear

From FlightGear wiki
Revision as of 11:47, 10 February 2012 by Hooray (talk | contribs) (→‎Background)
Jump to navigation Jump to search

Background

So called "fgcommands" are FlightGear extension functions which can be invoked in a number of different ways, such as embedded in various XML files (in the form of bindings), but also via Nasal scripts using the fgcommand() extension function. In addition, it is also possible to invoke fgcommands using the telnet/props interface, so that a simple form of "Remote Procedure Calling" can be implemented, i.e. triggering code via telnet to be run inside the FlightGear process.

An overview of fgcommands currently available and supported is provided in $FG_ROOT/Docs/README.commands.

Creating a new fgcommand is fairly simple actually. The source code you need to look at is in $FG_SRC/Main/fg_commands.cxx.


All commands are listed there, beginning in line #160. The signature of these callback functions is always such that they:

  • have static linkage
  • return a bool (true/false, 1/0)
  • accept one const pointer to a SGPropertyNode (const SGPropertyNode * arg)

The simplest command can be found in line 167, the do_null() command which does nothing:

/**
 * Built-in command: do nothing.
 */
static bool
do_null (const SGPropertyNode * arg)
{
  return true;
}


To see if that's actually doing something, you could add a printf or cout statement and rebuild FlightGear (you'll probably want to use a new git branch for these experiments, in order not to mess up your main branch - when doing that you'll just need to "git add fg_commands.cxx && git commit -m test" after making modifications):

/**
 * Built-in command: do nothing.
 */
static bool
do_null (const SGPropertyNode * arg)
{
  printf("Okay: doing nothing :)");
  return true;
}

Next, you could run the modified fgcommand by using the Nasal console (make sure to watch your console):

 fgcommand("null");

Argument processing

All callback functions need to accept a single argument, i.e. a const pointer: const SGPropertyNode*. This single argument is used to pass a property sub branch to each fgcommand. This means that each fgcommand is responsible for parsing and processing its function arguments.


For instance, imagine a command that accepts three different parameters to play an audio file:

  • path
  • file
  • volume

These all need to be first set up in the property tree separately, i.e. using setprop:

 setprop("/temp1/path","my path" );
 setprop("/temp1/file","filename" );
 setprop("/temp1/volumen", 1);
 fgcommand("null", "/temp1");


So, what the C++ code is doing once it is called, is getting the arguments out of the property argument, see line 1183:

 string path = arg->getStringValue("path");
 string file = arg->getStringValue("file");
 float volume = arg->getFloatValue("volume");

This is accomplished using the Property Tree API, i.e. the methods available in the SGPropertyNode class: http://simgear.sourceforge.net/doxygen/classSGPropertyNode.html

The most frequently used methods are:

  • bool getBoolValue () const: Get a bool value for this node.
  • int getIntValue () const: Get an int value for this node.
  • long getLongValue () const: Get a long int value for this node.
  • float getFloatValue () const: Get a float value for this node.
  • double getDoubleValue () const: Get a double value for this node.
  • const char * getStringValue () const: Get a string value for this node.

However, before actually trying to read in an argument, it is better to first check if he node is actually available, too:

 if (arg->hasValue("path"))
  string path = arg->getStringValue("path");

If an important argument is missing that you cannot set to some sane default value, you should show an error message and return false, to leave the function early:

 if (arg->hasValue("path"))
  string path = arg->getStringValue("path");
  else {
   printf("Cannot play audio file without a file name");
   return false;
  }

There is also a helper macro called SG_LOG available to print debug messages to the console:

 if (arg->hasValue("path"))
  string path = arg->getStringValue("path");
  else {
   SG_LOG(SG_GENERAL, SG_ALERT,"Cannot play audio file without a file name");
   return false;
  }

Adding new commands

All new commands must have the previously described signature, the functions should then be added to the list of built-in commands, beginning in line 1467. The list of built-in commands maps the human-readable names used in README.commands to the names of the internal C++ functions implementing them.

This was just an introduction intended to get you started, most of the missing information can be learned by referring to the plethora of existing fgcommands and see how they implemented and working.