AdaCore Blog

Unity & Ada

by Quentin Ochem


Using Ada technologies to develop video games doesn’t sound like an an obvious choice - although it seems like there could be an argument to be made. The reverse, however, opens some more straightforward perspectives. There’s no reason why a code base developed in Ada couldn’t benefit from a game engine to support the user interface. Think of a flight simulator for example, running a mission computer in Ada.

In the past few years, a number of these have been made available to a larger audience, mostly to serve the so-called indie scene. In this blog post, we’ll concentrate on Unity, which has the advantage of using the Mono environment as scripting language. As there is no flight mission computer at AdaCore, we’ll use a slightly modified version, the SPARK/Ada example already ported to a number of environments. So that’ll be a game in Ada after all!

Setup

You can get the code on AdaCore’s Github repository. It has been developed for Windows, but should be easily ported over to Linux (the directive “for Shared_Library_Prefix use "";” in the GNAT Project file may be all you need to remove - see more information in the Ada project setup). The Ada folder contains what’s necessary to build the native code, the TetrisUI contains the Unity project. You will need both GNAT and Unity for everything to work.

Unity can be downloaded from https://unity3d.com/. During the installation, make sure that you select the correct version - 32 bits or 64 bits - matching your GNAT compilation environment. In particular, to date, GNAT GPL is only provided with a 32 bits code generator on Windows, and 64 bits on Linux.

Once the Ada project is built, make sure to copy the resulting library under the Asset/Plugin directory of the Unity project. This will be described in more details below.

Now, let’s dive into the project.

Exporting Tetris Ada to C#

We’re going to skip the section describing the development of the Tetris code. There’s a full explanation available . Note that interestingly, we’re not only interfacing Ada, but actually SPARK code. This provides a good demonstration of a typical situation with Ada, where the core safety-critical functionalities are developed using a higher safety standard (here the core of the game), which can then be integrated in various environments with fewer safety constraints (here the user interface developed in Unity).

As said in the introduction, Unity runs mono - an open-source version of the .Net platform - and therefore allows you to develop scripts using the C# language. So, exporting our Ada code to Unity will turn out to be interfacing Ada and C#.

Step 1 - Setting up the Build

The first step is to set up a library to be loaded within Unity. We need actually several things here: the library has to be dynamic, we need it to be automatically initialized, and we need to make sure that it doesn’t have any dependency on any other libraries, in particular the GNAT libraries. This is achieved through the following settings in the GNAT Project file:

   for Library_Kind use "dynamic";
   for Library_Standalone use "encapsulated";
   for Shared_Library_Prefix use "";

We specify here the fact that the library is dynamic (for Library_Kind use "dynamic";) and that it should encapsulate all dependencies, in particular the GNAT library (for Library_Standalone use "encapsulated";). That second setting is important, otherwise our library would only work if the GNAT run-time is also provided.

By default, the build system (gprbuild) will add a “lib” prefix to the library name. I’m personally working on Windows and don’t fancy having this prefix which doesn’t really look natural. This behavior is cancelled by the clause (for Shared_Library_Prefix use "";). On Linux, where you’re likely to want this prefix, you may need to remove that clause.

Step 2 - Exporting to C, then C#

There’s no direct way to export from Ada to C#, but as the C# Platform Invoke (PInvoke) services allow us to call native C code as often, we’re going to go through C conventions for the interfacing. As a disclaimer, to anyone that is already shaking to the idea of interfacing virtual machines and native code - fear no more. Using PInvoke is surprisingly easier than using something like the Java Native Interface for the jvm - at least for basic interfacing. In our case, for Ada, it will be nothing more than exporting proper symbols.

In order to precisely control what gets exported, we’re going to wrap all calls to the Tetris package (that contains the game core) into a new package Game_Loop (see https://github.com/AdaCore/UnityAdaTetris/blob/master/Ada/src/game_loop.ads and https://github.com/AdaCore/UnityAdaTetris/blob/master/Ada/src/game_loop.adb). This package will have in particular a subprogram “Cycle” responsible for moving pieces around the board. Its interface will look as follows:

  procedure Cycle
     with Export,
     Convention => C,
     External_Name => "tetris_cycle";

I’m using Ada 2012 aspects here, as opposed to the usual “pragma Export”, but the effect is the same. This is declaring the C call convention, exporting the procedure to a C symbol “tetris_cycle”.

Importing this symbol into C# is painfully easy. All we need to do is create a static function of the right profile, and then associate it to the right DllImport directive. We’ll see how to set the C# project in the next section, but here’s the code that will have to be written in the class:

    [DllImport("tetris")]  
    private static extern void tetris_cycle();

And that’s it! We’ll of course need to make sure that the library is at the correct location to be loaded, but again, that’s for the next section.


One last point of importance - exceptions. The core of the game is written in SPARK and all run-time checks have been proven. So we know that when called properly (ie respecting preconditions) this code cannot raise any exception. However, there’s no such checks at the C# to Ada interface level and it’s quite possible that values passed from C# to Ada are wrong. A good example of this is board coordinates, which are constrained between 1 .. X_Size and 1 .. Y_Size but mapped into regular integers. Nothing prevents values in C# to be wrong, and thus to issue an exception when being passed to Ada. The resulting behavior is quite annoying as C# has no way to catch Ada exceptions - Unity will just crash. As it turns out, it happened to me quite a few times before I realized what was happening.

There are ways to throw a C# exception from native code - as to translate an Ada exception into a C# one. The SWIG interface generator does it for example. However, it’s outside of the scope of this blog, so we’ll just make sure that all calls that are part of the interface have default handlers that provide default behaviors. Let’s look at another example from the interface, the code that provides the value on a cell of the board:

  function Get_Kind (X, Y : Integer) return Cell
     with Export,
     Convention => C,
     External_Name => "tetris_get_kind";

 The implementation will look like:

   function Get_Kind (X, Y : Integer) return Cell is
   begin
      return Cur_Board (Y)(X);
   exception
      when others =>
         return Empty;
   end Get_Kind;

Returning the “Empty” literal is far from ideal. There’s no information passed to Unity that something wrong happened. However, this is enough to keep things going.

The C# code will look like:

    [DllImport("tetris")]
    private static extern byte tetris_get_kind(int x, int y);

Integer is quite logically mapped into int. I have to admit that I used implementation knowledge for the return type. I know that it’s a small enumeration that turns into an 8 bits unsigned int, hence the “byte” type. As it turns out however, this implementation knowledge is available to everyone. The -gnatceg switch from GNAT allows to generate a C interface to Ada. The resulting C header files can be directly used to develop C code or to interface with anything (like C#). And as a matter of fact, we could even have used these in the SWIG tool we mentioned before to automatically generate C# and - hey - use their interface to exception handling mechanism!

Back to our work, the last piece worth mentioning here is handling of data structures. More precisely, regarding a type in the Tetris specification:

   type Piece is record
      S : Shape;
      D : Direction;
      X : PX_Coord;
      Y : PY_Coord;
   end record;

We need to be able to access to this type from C#. Again, using -gnatceg we can see that the compiler generates a structure with two unsigned 8 bits integers and 2 regular integers. This is -non portable- insights on the compiler behavior. To do proper interfacing, it would probably have been better to declare this structure and all the types as being C convention. But we’re taking shortcuts here for the purpose of the demonstration.

Interestingly, this type can directly be mapped to C#. C# has two main composite data structures, the “class” and the “struct”. A notable difference is that “class” is pass-by-reference and “struct” pass-by-copy. So a C# struct is very appropriate here and can be directly mapped to:

    private struct Piece
    {
        public byte shape;
        public byte direction;
        public int x;
        public int y;
    }

So that the Ada call:

   function Get_Cur_Piece return Piece
     with Export,
     Convention => C,
     External_Name => "tetris_get_cur_piece";

Becomes:

    [DllImport("tetris")]
    private static extern Piece tetris_get_cur_piece();

One last piece of information, a number of calls are returning booleans value. Recent GNAT compilers will complain when interfacing Ada Boolean type with C - there isn’t such a type in C. Instead of just disregarding the warning we can shut it down by using our own Boolean type (with proper size clause this time):

     type CSharp_Bool is new Boolean with Size => 8;

Used in various places in the interfacing.

In summary, interfacing Ada and C# comes with a couple of hurdles, but is overall relatively painless. The code and this article take a number of shortcuts and present alternatives to the interfacing. Using this in an industrial context would require a bit more care to make sure that the interfaces are portable and safe. And even better, automatic generation of the wrappers and interface would be  ideal (a bit like GNAT-AJIS for Java). The question of exception handling still needs to be worked out but for a demonstrator, that’s good enough. We now have an Ada library ready to be used from within Unity. Let’s play!

Developing the game UI in Unity

Some high level information on Unity

There’s many things to know about Unity and we’re not even going to begin scratching the surface. For more detail, there’s a massive amount of tutorials and books available. We’re only going to provide a high level view of the concepts we deployed in this demonstrator. In order to run the example, the one thing you will need to do is to build the Ada library (see previous section) and to copy it to the Resources asset folder described below.

The first time a Unity project is open, it shows a game scene. There may be many scenes in a game, but for Tetris we’ll only need one. The objects of that scene are listed in the list on the left. These are the ones which are statically defined, but as we are also going to create a couple of objects dynamically, these objects are typed after GameObject. Unity is using an interesting component based design method, where each GameObject is itself composed of components (shape, renderer, position, scripts, etc). Clicking on the Main Camera object, we’ll get the list of these components on the right panel. In particular a Transform at the top, specifying matrix transformation for this object (position, rotation...), a Camera component for what is specific to the camera (in particular switching between orthographic and perspective projections), and at the end a script called Tetris, which will contain the behavior of the game. We decided to associate this script to the camera component, but as a matter of fact, it won’t have to interact with the camera, we could just as well have placed it anywhere.

At the bottom of the screen, we have the Assets. The components and game objects used to build the scene. Two scripts - Tetris that we discussed already and Cell which we’ll see briefly in a bit. A material called “CubeMat” used to render cubes and the Main scene. There are also two directories, Resources and Plugins. In the Asset folder, developers are mostly free of organizing elements the way they want apart from a couple of special directories, these two in particular.

The Plugins directory in particular is responsible for containing the dynamic libraries to be loaded by Unity. When checking the project out, it should be empty. Just drag and drop tetris.dll (or libtetris.so) here to complete the project.

Resources contains objects that can be dynamically instantiated. Here, Cell is going to be one cell on the screen. Clicking on it, you’ll see that it has a position, a collider, a renderer, a material, and a filter (responsible for the shape - or mesh - of the object). It’s also associated with a script also called Cell, which implements some specific services.

And that’s pretty much it. Clicking on the play button on the top, you should be able to launch the game, move bricks with the arrows or accelerate the fall with the space key. Note that stopping and relaunching the game from within the same Unity instance will not reset the board. This is because the library is actually loaded within the Unity environment itself, not in response to the play button. To reset, we can either re-launch Unity, or extend the implementation with some initialization code launched at game start.


The Tetris behavior

The UI for this Tetris is very basic. It’s a 10 * 38 grid of pre-loaded cubes, that we’re going to make appear and disappear depending on the state of the core game. It’s arguably a bit of a shame to use a tool such as Unity and turn it into such a basic implementation - but it has the benefit of adhering to the core developed in Ada, which can then be ported to much more crude environments. It’s all ready to be extended though!

The core script is pretty much all contained in this “Tetris.cs” script, which you can open either from the asset list or by clicking on the entry in the Main camera. You’ll see a bunch of DllImport clauses here, similar to those we described in the previous section. Next is a call to Awake(). Awake is a special function that unity calls once the object has been created and set up. It’s just recognized by Unity from its name, there’s no overriding mechanism used here (although of course C# has support for it). The first line is getting a handle on a prefab, which is one of these resources we’re going to create dynamically:

   prefabBlock = Resources.Load("Cell") as GameObject;

The complete path to the prefab is “Assets/Resources/Cell” but the common prefix is omitted. Once this handle is obtained, I can then instantiate it through instantiate calls:

   Instantiate(prefabBlock)

Which returns a GameObject. As seen before, this GameObject is associated to a script Cell. In Unity, all these scripts are actually descendent of the type Behavior or MonoBeharior. What’s nice about the component based model of unity is that it’s always possible to get any components of a given object from any other component. In other words, I can store this object through a reference to a GameObject or using its Cell directly.

When only one component of a specific type is available, it can be accessed through the generic GetComponent<type>() call, so:

    cells[x, y] = Instantiate(prefabBlock).GetComponent<Cell>();

actually initializes the array cells at x,y position with the instance of the type Cell from the prefab I just initialized. Next we’re going to deactivate the cell for now, as the grid is empty by default. See that we can reference the GameObject from the Cell directly:

    cells[x, y].gameObject.SetActive(false);

The rest of the code is pretty straightforward. Update() is another of these magic functions, this one is called at every frame. We’re calling tetris_cycle() regularly to update the game, and subsequently activating / deactivating cells depending of the status of the board.

The Cell script has some other interesting elements. The first thing it does (in Awake) is to duplicate the material in order to be able to change its color independently of others. The color is then changed in the Renderer component from SetKind. Last but not least, the Explode function creates an instance of the explosion particle system to give a little something when a line is destroyed.

Going Further

There are various things to play with from there. The game itself is merely a proof of concepts, and many things can be added starting with a button to reset the game, a button to exit it, score, etc. The Ada to C# interfacing techniques can also be greatly improved, ideally through automatic binding generation. And of course, it would be nice to have a more comprehensive piece of Ada code to integrate into a larger scale project. All the pieces are here to get started!


Posted in #GitHub    #Ada    #GNAT    

About Quentin Ochem

Quentin Ochem

Quentin Ochem is the Chief Product and Revenue Officer at AdaCore, overseeing marketing, sales, and product management. His involvement with AdaCore began in 2002 during his school years, officially joining in 2005 to work on IDE and cross-language bindings. Quentin has a background in software engineering, particularly in high-integrity domains like avionics and defense. His roles expanded to include training and technical sales, leading him to build the technical sales department and global product management in the US. In 2021, he stepped into his current role, steering the company’s strategic initiatives.

Quentin holds a master's degree in Computer Engineering from Polytech Marseille, awarded in 2005.