Howto:Using Ada in FlightGear
Note Contributors wanting to check out the topics/ada-support flightgear branch, may follow these 3 steps
to set up a remote-tracking branch:
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 |
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 |
Multicore |
---|
Configuration |
Ongoing Efforts |
Proposals & RFCs |
Background |
For developers |
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:
- https://sourceforge.net/p/flightgear/mailman/message/35734446/
- https://sourceforge.net/p/flightgear/flightgear/ci/6183beec38985590efb0850c611b21b4a337bc59/
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
- try mixing C++ and Ada code in $FG_SRC Done
- explore implementing fgcommands via Ada
- explore implementing SGSubsystems via Ada
- read up on creating thin bindings automatically via -fdump-ada-spec Not done
- see if/how to integrate this into cmake Not done
- create some bindings for OpenRTI/HLA 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 |
SGPropertyNode | FlightGear Property Tree | Not done |
fg_props.hxx | FlightGear Property Tree / XML (see $FG_SRC/Main/fg_props.cxx) | Not done |
fgcommand/SGCommandMgr | FlightGear Command Mgr (see $FG_SRC/Main/fg_commands.cxx) | Not done |
naCFunction | Nasal extension functions (via Nasal/CppBind?) | Not done |
SGSubsystemMgr | FlightGear Subsystem APIs | Not done |
FGFDMInterface | FlightGear FDM Interface | Not done |
HLA/RTI | see FlightGear HLA support (High Level Architecture) | 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
|
- ↑ James Turner (Mar 18th, 2017). Re: [Flightgear-devel] Crash in routePath.cxx (#1703) .