Howto:Using Ada in FlightGear

From FlightGear wiki
Jump to navigation Jump to search


Note  Contributors wanting to check out the topics/ada-support flightgear branch, may follow these 3 steps

to set up a remote-tracking branch:

  • git remote add hoorays https://gitorious.org/fg/hoorays-flightgear.git
  • git fetch hoorays
  • git checkout --track hoorays/topics/ada-support topics/ada-support

This will give you a local branch named topics/ada-support, so that you can easily pull/push changes. To learn more about building FlightGear, see Superbuild.

To enable the Ada build, reconfigure cmake using -DENABLE_ADA_MODULES=ON, which will require a working gcc/gnat installation then (on Ubuntu: sudo apt-get install gnat).

This is optional, but to speed up compilation, you may want to install ccache/distcc prior to running cmake or disable features that you don't need, for example using:

cmake $FG_SRC -DCMAKE_INSTALL_PREFIX=$FG_INSTALL -DENABLE_FGCOM=OFF -DENABLE_FGELEV=OFF 
-DENABLE_FGJS=OFF -DENABLE_FGVIEWER=OFF -DENABLE_FGADMIN=OFF -DWITH_FGPANEL=OFF 
-DENABLE_TERRASYNC=OFF -DENABLE_IAX=OFF -DENABLE_FLITE=OFF

Also see Building using CMake

Ada in FlightGear
FlightGear built using gnatmake.png
Started in 09/2014
Description Supporting use of Ada in FlightGear
Maintainer(s) Hooray, onox
Contributor(s) Hooray, onox
Status working, but still experimental (as of 09/2014)
Topic branches:
$FG_SRC https://gitorious.org/fg/hoorays-flightgear?p=fg:hoorays-flightgear.git;a=log;h=refs/heads/topics/ada-support


Status (09/2014)

The FlightGear build system has been extended to support an optional build mode that allows mixing Ada/C++ code easily. Ada features like tasking have been shown to work properly. The source tree contains a few examples on calling Ada code from C++ and vice versa. The main thing missing now are wrappers for existing SimGear/FlightGear APIs like the property tree, so that Ada code can be interfaced to subsystems, or used to implement new subsystems. All changes are completely optional and do not affect the default, non-Ada, build. People interested in exploring Ada use in FlightGear are encouraged to get in touch with Hooray or onox using either the FlightGear forum or the wiki to coordinate all related efforts.


See also:

Objective

We're hoping to document the steps required to extend our CMake build system to support optional modules implemented in Ada as discussed by onox, Flying toaster and Hooray on the forum, including support for mapping existing C++ classes to Ada (e.g. from OSG and/or OpenRTI).

The main goal being here to leverage Ada's native tasking support for prototyping features that do not have to be running in the FlightGear main loop.

We've already found some existing CMake modules that we can reuse: The PLPlot project is implemented using C, C++ and Ada and provides a set of CMake modules that are scheduled to be merged into CMake soon.

Hooray has copied those files into a branch of $FG_SRC and ended up with a working build environment that successfully links in Ada modules. For starters, we'll be playing with optionally allowing fgcommands to be implemented in Ada, as well as new SGsubsystems.

Next, we'll be exploring how to add thin bindings using -fdump-ada-specc for existing SimGear/OpenRTI (HLA) headers and document the whole process here.

Using -fdump-ada-specc to create bindings for SG/HLA classes to create standalone HLA federates in Ada would make FG pretty compelling for anybody doing this kind of things professionally, because Ada simply happens to be widely used in the aviation industry, and it seems there are even bindings of complex stuff like OSG available.

Why Ada ?

We've seen a number of contributors on both, the devel list and the forum, mention Ada and its benefits when it comes to concurrent programming.

C++ support for tasking/multi-threading is generally considered fairly "low-level". Other programming languages, like Ada, provide native support for tasking using built-in primitives like tasks and protected types.

Kernel threads are too low-level as a concurrency primitive for any non-trivial application - which is why people tend to come up with all kinds of design patterns to encapsulate different kinds of threading models and to better understand how these can inter-operate - which is why there are more and more languages that have native support for parallelism, and synchronization, without involving manual mutex/semaphore/join management.

The other good thing about Ada is that it is designed to work well with existing C/C++ code - which is how the GNAT front-end is integrated with gcc's middle/back-end (code generator/optimizer and linker dealing with various tree representations ).

In other words, it would actually be possible to partially modernize/re-implement certain unmaintained C++ code using Ada and still link the whole thing - on Linux, it's fairly straightforward to link a heterogeneous code base that consists of C, C++ and Ada sources - but I don't know about the Windows side of things, where building FG can already be a nightmare.

But apart from Java, and its obvious JRE/GC issues, Ada would probably seem like one of the more obvious candidates for implementing a real-time app/simulator like FlightGear these days, i.e. without being overly "niche", but even pretty relevant to aviation/avionics manufacturers who are likely to already be familiar with Ada-based projects - the syntax is straightforward, almost BASIC/PASCAL-like .

And Ada has also ben getting quite some mention on the OSG mailing list, for pretty much the same reasons that people keep mentioning Ada on the FlightGear devel list.

In other words, increasingly adopting Ada in FlightGear would probably not even be such a bad thing, as long as the build system can deal with (i.e. cmake) and people on Windows find a good way to still build FG.

Overall, it seems unfortunate that there's only a tiny fraction of FlightGear written in Ada (i.e. a single FDM) - otherwise, FlightGear could easily be more relevant to industry leaders in the aviation/aerodynamics, but also realtime simulation world.

However, given that there's ongoing work on HLA, it seems foreseeable that there'll be better ways to interface Ada code with FlightGear. Somewhere in the OpenRTI sources, Mathias specifically invites people to help provide OpenRTI bindings for Java and Ada.

Basically, people doing professional real-time simulation will not be bothered too much by a niche language language like Nasal, but will be much more affected by FlightGear's much more dominant C++ issues, especially the way the threading architecture has never been formalized (as can be currently seen on the devel list), but also the way all systems/scripts are running right in the rendering/main loop. Ada would really only help with the main loop itself, but for general purpose concurrency, it would make sense to use OpenCL bindings for better parallelism - while those could still be exposed via Nasal, people would have no easy way to mess up concurrency that way because OpenCL forces you to think about the problem.

FlightGear Build System Integration (cmake)

The CMake/Ada modules provided by PLPlot have a number of hard-coded constraints, including the assumption that the main executable is written in Ada, which requires some creative workarounds

The current integration is kind of a workaround using the following approach (one also useful for Testing [1]):

  • Ada support needs to be explicitly enabled using this cmake configuration flag: -DENABLE_ADA_MODULES=ON
  • we're using the PLPlot CMakeModules, copied them into $FG_SRC/CMakeModules
  • what used to be fgfs is now instead linked into a library called libMain
  • main is renamed and extern'ed "C" to disable C++ name mangling
  • the fgfs executable itself is now an Ada stub that imports main() from libMain (aka FlightGear) using pragma/import
  • cmake's add_library() is used to link libMain (FlightGear as a library) into the Ada executable/stub
  • linking is delegated to gnatmake this way, we can easily mix C, C++ and Ada sources this way by simply using cmake as before (see Developing using CMake)

Roadmap

  • research integrating Ada into our existing CMake build system Done Done
  • try mixing C++ and Ada code in $FG_SRC Done Done
  • explore implementing fgcommands via Ada 60}% completed
  • explore implementing SGSubsystems via Ada 20}% completed
  • read up on creating thin bindings automatically via -fdump-ada-spec Not done Not done
  • see if/how to integrate this into cmake Not done Not done
  • create some bindings for OpenRTI/HLA Not done Not done


STL Containers

Both, SimGear and FlightGear, are using the C++ STL in a number of places, including some key APIs. Thus, it makes sense to provide wrappers to access the most common containers, such as:

  • std::string
  • std::vector
  • std::map

FlightGear APIs

Tip  If you are not yet familiar with the way FlightGear is structured, you may want to first check out Howto:Start core development#Project_architecture

FlightGear's architecture is primarily based on subsystems that are linked together using the Property Tree, the property tree is populated using XML files, scripts and C++ subsystems, the top-level C++ API here is SGPropertyNode in $SG_SRC/props/props.?xx

All subsystems expose an abstract interface, notably an update() method - subsystems generally use "cooperative multi-tasking", i.e. time-slicing/sharing - subsystems are registered to be part of a SGSubsystemMgr instance, which manages a group (i.e. std::vector<SGSubsystem*>) of related subsystems - the top-level subsystem in FlightGear is such a manager system that simply keeps updating each registered member's update() method in a loop. With the exception of FDM-coupled subsystems (autopilot, systems, instrumentation), all subsystems will be normally updated once per frame. The subsystem APIs are to be found in SimGear, i.e. $SG_SRC/structure

Increasingly, subsystems tend to implement listener-based systems on top of the property tree using the SGPropertyChangeListener API so that subsystems can get notifications whenever a certain property/location is modified - this is how the models/AI traffic/Canvas systems work without requiring a dedicated C/C++ or Nasal API, the "public API" is entirely modeled on top of the property tree, where each subsystem has its own branch in the top-level property tree (/models, /ai/ and /canvas here respectively). Other code running in the main loop can directly leverage such systems, which is how Nasal can be used to interface with such systems - which would also work for Ada obviously.

Commands can be executed using so called fgcommands that are typically triggered in keyboard/mouse bindings. Implementing such fgcommands in Ada is straightforward and doesn't require much in terms of APIs, except for SGPropertyNode bindings.

More complex functionality is usually implemented through Nasal scripts, Nasal can be extended through extension functions implemented in C, C++ or Ada. The main mechanism for running Nasal code within the main loop are listeners and timers. Since the adoption of Canvas, C++ APIs exposed to Nasal are increasingly no longer just procedural but object-oriented (and lazy) by using the Nasal/CppBind framework for making complete classes and objects available. The legacy extension mechanism still works and would be straightforward to use from Ada, even though creating thick bindings for Nasal/CppBind would probably be desirable due to increased flexibility, and be less work for people adding new APIs.

IPC is generally handled using the property tree. The multiplayer system is using XDR/TinyXDR-encoded properties for cross-platform property IPC across UDP sockets, while multi-instance setups can be set up in a master/slave fashion using a NetFDM struct (also using sockets). There are competing, and even conflicting, features to some degree that were never integrated. But core developers have been working towards supporting/adopting HLA in FlightGear: FlightGear HLA support (High Level Architecture).

The FlightGear main loop is primarily single-threaded - individual subsystems (e.g. sound) may be using the SGThread class to run some processing asynchronously/in worker threads. For the time being, subsystems are generally only safe to be accessed from the main loop/thread, which includes native code, but also Nasal scripts - despite Nasal itself having supported multi-threading, only Nasal library functions are safe to be accessed from multiple Nasal threads. The osgviewer-based renderer is also primarily single-threaded - however, OSG may internally use threading to delegate some processing to worker threads.

Care must be taken that Reset & re-init keeps on being explicitly supported by any future additions, regardless of the implementation - including C++, Nasal and Ada code obviously.

Thus, for Ada support in FlightGear to be any useful, we need to expose a handful of FlightGear APIs and make them accessible from Ada.

For starters, this would mainly include APIs like these (listed in ascending complexity):

API Description Status
SG_LOG FlightGear Logging macro Not done Not done
SGPropertyNode FlightGear Property Tree Not done Not done
fg_props.hxx FlightGear Property Tree / XML (see $FG_SRC/Main/fg_props.cxx) Not done Not done
fgcommand/SGCommandMgr FlightGear Command Mgr (see $FG_SRC/Main/fg_commands.cxx) Not done Not done
naCFunction Nasal extension functions (via Nasal/CppBind?) Not done Not done
SGSubsystemMgr FlightGear Subsystem APIs Not done Not done
FGFDMInterface FlightGear FDM Interface Not done Not done
HLA/RTI see FlightGear HLA support (High Level Architecture) Not done Not done

Creating Bindings

Note  Discuss gcc's -fdump-ada-spec vs. the patched SWIG[1]

Important Macros (CMake/C++)

Use the ENABLE_ADA_MODULES cmake macro to check if the Ada build is enabled

if(ENABLE_ADA_MODULES)
 message(STATUS "Ada build mode enabled !");
endif(ENABLE_ADA_MODULES)

or to add a directory with sources conditionally:

if(ENABLE_ADA_MODULES)
  add_subdirectory(AdaTest)
endif(ENABLE_ADA_MODULES)


Use the FG_ENABLE_BUILD macro in your C/C++ code to check if the Ada build mode is selected:

#if FG_ENABLE_ADA_BUILD
extern "C" {
       SGSubsystem* createAdaSubsystem();
}
#endif

Adding Ada sources to FlightGear

For directories with just Ada sources, use the add_subdirectory construct seen above. For mixed C/C++ and Ada directories, you need to add your modules conditionally by appending to the SOURCES list:

if(ENABLE_ADA_MODULES)
  append(LIST SOURCES myAdaModule.adb myAdaModule.ads)
endif(ENABLE_ADA_MODULES)

Exposing C++ symbols to Ada

Exposing Ada symbols to C++

To export Ada symbols to C++, use the export pragma and specify your internal Ada name, as well as the external name to be used by the C++ code:

-- define a prototype for a procedure (void)
procedure myProcedure;
-- expose the procedure to the C++ code
pragma Export
( Convention    => CPP,
  Entity        => myProcedure,
  External_Name => "myAdaProcedure"
);

Accessing C++ Classes

Implementing C++ Interfaces

Accessing FlightGear's Globals

Automatically creating stubs

Tip  You can use the gnatstub tool to automatically create bodies/stubs from your spec files (sudo apt-get install asis-programs, use yum/zypper respectively)
package Test is

type Position3D is 
record
 x,y,z: Float;
end record;

procedure setX(this: in out Position3D; value: in Float);
procedure setY(this: in out Position3D; value: in Float);
procedure setZ(this: in out Position3D; value: in Float);
end Test;

Running gnatstub, will automatically create a stub for this package:

package body Test is

   ----------
   -- setX --
   ----------

   procedure setX (this: in out Position3D; value: in Float) is
   begin
      --  Generated stub: replace with real body!
      pragma Compile_Time_Warning (True, "setX unimplemented");
      raise Program_Error;
   end setX;

   ----------
   -- setY --
   ----------

   procedure setY (this: in out Position3D; value: in Float) is
   begin
      --  Generated stub: replace with real body!
      pragma Compile_Time_Warning (True, "setY unimplemented");
      raise Program_Error;
   end setY;

   ----------
   -- setZ --
   ----------

   procedure setZ (this: in out Position3D; value: in Float) is
   begin
      --  Generated stub: replace with real body!
      pragma Compile_Time_Warning (True, "setZ unimplemented");
      raise Program_Error;
   end setZ;

end Test;

SGPropertyNode

This is the top-level Property Tree API, for the SimGear implementation, refer to [2]. For starters, the most important methods would be:

  • getNode()
  • get*Value() / set*Value()

SGCommandMgr bindings

We'll want to be able to implement fgcommands in Ada using the equivalent of this:

with Ada.Text_IO; use Ada.Text_IO;

-- thin bindings created by -fdump-ada-specc
with SimGear.SGPropertyNode; use SimGear.SGPropertyNode;

function do_hello(node: in SGPropertyNode)
 Ada.Text_IO.Put_Line("Hello ...");
 return true;
end do_hello;


SGSubsystem bindings

Note  All Ada subsystems will need to interact properly with the sim and its main events using listeners (/sim/signals) - therefore, it makes sense to introduce a wrapper interface for an abstract AdaSGSubsystem that implements SGPropertyChangeListener so that all Ada subsystems must implement event handlers to handle reset/re-init properly, or Ada subsystems would inevitably end up being "2nd class" sooner than later.

Thus, it might make sense to directly implement the corresponding SimGear API: PropertyBasedMgr.


Need to be able to implement SGSubsystems using something along the lines of this (pseudo code):

-- subsystem.ads
with Interfaces.C; use Interfaces.C;

package Subsystem

 -- see $SG_SRC/structure/sg_subsystem_mgr.hxx
 type SGSubsystem is abstract tagged limited 
 record
   null; -- TODO: look up fields
 end record;
 pragma Import (CPP, SGSubsystem);

 -- create a new subsystem inheriting from SGSubsystem
 type mySystem is new SGSubsystem with null record;
 procedure update(this: mySystem, dt: double);

 -- allocator
 function createAdaSubsystem return access mySystem;
 pragma Export (CPP, createAdaSubsystem, "createAdaSubsystem");
end Subsystem;
-- subsystem.adb
with Ada.Text_IO; use Ada.Text_IO;
package body Subsystem
 -- implement the update method used by this SGSubsystem instance
procedure update(this: mySystem, dt: double) is
begin
 Put_Line("Ada subsystem.update() called");
end update;

 -- allocator
function new_mySystem return access mySystem is
begin
 return new mySystem;
end new_mySystem;
end Subsystem;


Once we have thin bindings for the SGSubsystem API, we can then directly instantiate subsystems in $FG_SRC/Main/fg_init.cxx:

diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx
index 8efcb80..f292cd6 100644
--- a/src/Main/fg_init.cxx
+++ b/src/Main/fg_init.cxx
@@ -149,6 +149,12 @@
 #include <GUI/CocoaHelpers.h> // for Mac impl of platformDefaultDataPath()
 #endif
 
+#if FG_ENABLE_ADA_BUILD
+extern "C" {
+       SGSubsystem* createAdaSubsystem();
+}
+#endif
+
 #define NEW_RESET 1
 
 using std::string;
@@ -909,6 +915,13 @@ void fgPostInitSubsystems()
     st.stamp();
     globals->get_subsystem_mgr()->postinit();
     SG_LOG(SG_GENERAL, SG_INFO, "Subsystems postinit took:" << st.elapsedMSec());
+
+#if FG_ENABLE_ADA_BUILD
+    globals->add_subsystem("ada-demo", createAdaSubsystem() );
+    SG_LOG(SG_GENERAL, SG_ALERT, "Adding experimental Ada subsystem");
+#endif
   
     ////////////////////////////////////////////////////////////////////////
     // End of subsystem initialization.

Resources

References