Dependency Injection in MonoGame

Posted with : Game Development, Game Loop

In the previous article of this mini-series I talked about the problems inherent in MonoGame (and other Game Engines) that effectively prevent Dependency Injection (DI). During the development of Clean Space I made several attempts to resolve this particular issue, and in this article I want to describe the first (pretty crude) mechanism I put together.

yuck

I’ll also explore (and prove) the performance considerations of making changes like this to MonoGame, whilst contrasting this dependency injection pattern to the many service locator anti-patterns I’ve seen used as workarounds.

First World Problems

So the issue with MonoGame, and I’ll keep reiterating that it’s not limited to just MonoGame (don’t get me started on Unity), is that there are no abstractions, and many classes have inter-dependencies. That is to say that class A depends on class B, and class B depends on A. This problem very quickly rears it’s ugly head if you follow the application bootstrapping code, as demonstrated in the previous post.

Throughout this article I’ll be working exclusively with v3.7.1 of MonoGame, but the Universal Windows Platform (or ‘UWP’ for short) XAML project is much the same in the latest ‘dev’ branch if you’re inclined to be a bit more cutting edge. As such, let’s recap and explore the application startup of a UWP XAML project.

Note: ‘XAML’ stands for ‘eXtensible Application Markup Language’ and is an XML-based language designed by Microsoft to describe the visual components of your Application.

Startup (XAML)

Each platform MonoGame supports has a slightly different startup mechanism, primarily dictated by the Windows and .NET Framework calls that need to be made in order to get an application up and running. In the case of a UWP XAML application, the first bit of code to run (that we can see) is that contained in App.xaml.cs.

Whilst this class contains around 130 lines of code by default (from the project template) most of that isn’t too important with regards to Dependency Injection. The only line that really matters to us right now is:

1
rootFrame.Navigate(typeof(GamePage), e.Arguments);

Note: Whilst it might seem I’m ripping into MonoGame for their lack of DI support in this article, it’s worth mentioning that Microsoft are no better - they provide basically no DI support in UWP itself.

During startup, because nothing has been navigated to or displayed yet, the application will transition to GamePage. At this point, the code in GamePage.xaml.cs will be executed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public sealed partial class GamePage : Page
{
  readonly Game1 _game;

  public GamePage()
  {
    this.InitializeComponent();

    // Create the game.
    var launchArguments = string.Empty;
    _game = MonoGame.Framework.XamlGame<Game1>
      .Create(launchArguments, Window.Current.CoreWindow, swapChainPanel);
  }
}

This is the first point at which MonoGame gets involved. Here we can see that it is trying to load the Game1 class into a SwapChainPanel (which the project template has declared for you in GamePage.xaml).

Before we take a look at the Game1 class, let’s have a look at the code that executes within the XamlGame<Game1>.Create method:

1
2
3
4
static public T Create(string launchParameters, CoreWindow window, SwapChainPanel swapChainPanel)
{
    return Create(() => new T(), launchParameters, window, swapChainPanel);
}

This version of the Create method was actually introduced by Francesco Bonizzi in October 2018. This greatly simplifies (though doesn’t solve) our DI considerations when compared to earlier versions. You see, whilst by default your Game class will be instantiated with new T(), we can see that there is an overloaded method of the Create method which accepts a Func<T>.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static public T Create(Func<T> gameConstructor, string launchParameters, CoreWindow window, SwapChainPanel swapChainPanel)
{
  //argument validation...

  // Save any launch parameters to be parsed by the platform.
  UAPGamePlatform.LaunchParameters = launchParameters;

  // Setup the window class.
  UAPGameWindow.Instance.Initialize(window, swapChainPanel, UAPGamePlatform.TouchQueue);

  // Construct the game.
  var game = gameConstructor();

  // Set the swap chain panel on the graphics mananger.
  if (game.graphicsDeviceManager == null)
      throw new NullReferenceException("You must create the GraphicsDeviceManager in the Game constructor!");
  game.graphicsDeviceManager.SwapChainPanel = swapChainPanel;

  // Start running the game.
  game.Run(GameRunBehavior.Asynchronous);

  // Return the created game object.
  return game;
}

There’s a lot more happening here than everything else we’ve seen so far. We can ignore the argument validation (excluded for brevity above) and jump straight to UAPGamePlatform and UAPGamePlatform. ‘UAP’ is short for Universal Apps which is what we called ‘UWP’ a few years ago when the concept was first introduced. The classes in question actually belong to the XNA namespace (not MonoGame) which is a game framework Microsoft stopped developing several years ago before the MonoGame community picked up the reins.

Note: ‘XNA’ is not an acronym. Greg Duncan explained that if anything, ‘XNA’ stands for ‘XNA’s not acronymed’…

not funny

…but at least it’s one less acronym from Microsoft to try and remember.

The Initialize method on the UAPGameWindow class is also very involving, not to mention internal so is otherwise not visible to us.

Once the XNA components are configured, MonoGame goes on to instantiate our Game1 class (passed as a generic T), then complain (by throwing an exception) if you don’t set the graphicsDeviceManager property (which is also internal…) before calling the public Run method on the inherited Game class. Remember this bit because it’ll become important later.

The reason I keep highlighting these internal members is it means, unless we’re writing code in the same assembly as MonoGame, or MonoGame for some reason exposes it’s internal members to our code using an InternalsVisibleTo attribute, we can only access those classes via reflection, and doing so would not be type safe. This is a huge problem with regards to extensibility because it makes it either impossible or very unsafe to extend and change behaviours.

not happy

Interdependencies

Of the last block of code we looked at, the biggest problem by far is interactions with the graphicsDeviceManager property on the Game class. The property looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
internal GraphicsDeviceManager graphicsDeviceManager
{
    get
    {
        if (_graphicsDeviceManager == null)
        {
            _graphicsDeviceManager = (IGraphicsDeviceManager)
                Services.GetService(typeof(IGraphicsDeviceManager));
        }
        return (GraphicsDeviceManager)_graphicsDeviceManager;
    }
    set
    {
        if (_graphicsDeviceManager != null)
            throw new InvalidOperationException("GraphicsDeviceManager already registered for this Game object");
        _graphicsDeviceManager = value;
    }
}

There seems to be some sort of in-joke about setting important fields as internal and not making them virtual, but I understand that this property is so intrinsic to other code in MonoGame that they wanted to protect it. We can see from the getter that if the private field _graphicsDeviceManager is null then it will populate the field using Services.

We haven’t talked about this Services property yet but we’ll circle back to it later. We can also see from the setter that if we make the mistake of attempting to set the graphicsDeviceManager property twice, we’ll get an InvalidOperationException.

With that all in mind, let’s take a look at this GraphicDeviceManager class, and in particular, it’s constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public GraphicsDeviceManager(Game game)
{
    if (game == null)
        throw new ArgumentNullException("game", "Game cannot be null.");

    _game = game;

    // sets a load of graphics properties which I'll omit

    // Let the plaform optionally overload construction defaults.
    PlatformConstruct();

    if (_game.Services.GetService(typeof(IGraphicsDeviceManager)) != null)
        throw new ArgumentException("A graphics device manager is already registered.  The graphics device manager cannot be changed once it is set.");
    _game.graphicsDeviceManager = this;

    _game.Services.AddService(typeof(IGraphicsDeviceManager), this);
    _game.Services.AddService(typeof(IGraphicsDeviceService), this);
}

If you scroll up to the previous chapter, you may recall that graphicsDeviceManager was a property on the Game class (I told you that bit was important). During our exploration of the Create method we saw that if you don’t set that property when constructing your Game then it will throw an exception. Here’s a mini-recap:

1
2
3
4
5
6
// Construct the game.
var game = gameConstructor();

// Set the swap chain panel on the graphics mananger.
if (game.graphicsDeviceManager == null)
    throw new NullReferenceException("You must create the GraphicsDeviceManager in the Game constructor!");

For those with experience with DI containers, you’ll immediately see the issue here. In order to construct a Game, we must create GraphicsDeviceManager, but in order to construct a GraphicsDeviceManager, we must create a Game. Depending on the DI container you use, you’ll either end up with a StackOverflowException as the container loops around playing hot-potato with these constructors, or the container will realise that neither class can be instantiated and throw an exception accordingly.

Submodules

There are a number of approaches to tackling the above problems (and other problems we haven’t discovered yet), but the first one I’ve opted for here is to use a git submodule to pull in the MonoGame source code, have my project reference the submodule, and then hack away at it until things are working.

Note: I don’t want to spend time explaining submodules and how to work with them, so if it’s a concept you’re not familiar or comfortable with, I recommend checking out a tutorial and then heading back here afterwards.

Before I get started on this approach though, I want to say that this is a bad idea. By submoduling the MonoGame repository I’m introducing a fork that I then become responsible for maintaining (like applying the latest commits from the parent). On a large active repository like MonoGame, it really isn’t very workable. However, it is a useful mechanism for exploring problems with frameworks you depend on.

Per the MonoGame README, once you’ve initialised the MonoGame submodule and in turn, MonoGame’s submodules, you can run Protobuild.exe to generate the necessary csproj’s. Protobuild will generate a MonoGame.Framework.WindowsUniversal.csproj in the MonoGame.Framework directory, which we can add to our solution as an existing project:

monogame submodule

Note: Once you’ve submoduled MonoGame, create yourself a branch at the v3.7.1 tag and checkout there, otherwise you’ll default to the develop branch which has been set as the default.

With the MonoGame source code now available, drop the assembly reference to MonoGame.Framework in your own application, and add a project reference to MonoGame.Framework.WindowsUniversal in it’s place. Your application should still build and be able to execute (though in my case I do get a bunch of build warnings from the MonoGame project which I’ll ignore).

Whilst that seems like a lot of work to have achieved nothing, what it actually means is that we can now edit the MonoGame code and see that reflected immediately in our own application. Given the power and authority to change MonoGame code, the next sections outline the changes I would make in order to better support dependency injection.

Initialisation

You may remember from my previous post there’s actually two aspects of dependency injection I would like to improve. The first is constructor injection for my Game class during initialisation (when the game first loads, or perhaps resumes from sleeping in some cases). The second case was scoped convention injection during each iteration of the loop (each time the Update method is called).

The two mechanisms are very different so we’ll tackle each in turn. Whenever you make a change to an application, there are several things you need to take into account, one of which is performance. I could spend time benchmarking application initialisation times before and after my changes, but the fact is, if your game reads any file (textures, sprites, audio clips, etc) or pops out to the internet, which is very likely when you’re loading a game, the minuscule amount of time it takes to run dependency injection by comparison makes it a complete non-entity. As such, I’m not even going to consider performance for game initialisation. However, when we look at interfering with the update loop, which runs very often and needs to execute very quickly, we’ll definitely spend some time benchmarking.

When following the startup code, we saw that the execution flow is roughly:

App.xaml.csGamePage.xaml.csGame1.csGraphicsDeviceManager.cs

As we discovered above, the GraphicsDeviceManager requires a reference to Game1. Normally, this would be a good thing. We call it dependency inversion, however the two classes are very strongly-coupled.

For Application Initialisation it’s actually pretty simple to hook in a DI container if we accept the caveat that the constructor for Game1 must always instantiate the GraphicsDeviceManager. To prove this, let’s install the Microsoft.Extensions.DependencyInjection NuGet package.

Note: In order to install this package, per the NetStandard Compatibility Documentation, you need to ensure your minimum UWP target is the Falls Creators Update (Windows 10.0 Build 16299).

Once installed, modify GamePage.xaml.cs to read as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public GamePage()
{
    this.InitializeComponent();

    // Create the game.
    var launchArguments = string.Empty;

    // create an MS service collection
    var services = new ServiceCollection();
    // add our game class
    services.AddSingleton<Game1>();
    // Our game class creates the GraphicsDeviceManager, which in turn adds itself to the Game.Services property
    // we can peal those services back out by fetching our singleton Game1 instance:
    services.AddSingleton(provider => provider
        .GetRequiredService<Game1>()
        .Services
            .GetService<IGraphicsDeviceManager>());
    services.AddSingleton(provider => provider
        .GetRequiredService<Game1>()
        .Services
            .GetService<IGraphicsDeviceService>());
            
    // add your custom services here...
            
    // build a microsoft di container
    var serviceProvider = services.BuildServiceProvider();
     // pass a delegate of our di container to the Create method overload
    _game = MonoGame.Framework.XamlGame<Game1>.Create(serviceProvider.GetRequiredService<Game1>, launchArguments, Window.Current.CoreWindow, swapChainPanel);
}

Hopefully the code comments cover the basics in that snippet. We can confirm this works by adding a dependency to our Game1 constructor:

1
2
3
4
5
6
7
8
public Game1(Foo foo)
{
    _foo = foo;

    // this line must be kept despite the DI, sorry!
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";
}

Then add a Foo implementation to our DI container in the GamePage.xaml.cs class prior to the DI container being created:

1
2
// add your custom services here...
services.AddSingleton<Foo>();

But wait, we now have an error in this class. The generic on the XamlGame class is declared as where T : Game, new().

ffs

Well, if you’re only after a hacky application initialisation, you can update the constructors on Game1 to be as follows:

1
2
3
4
5
6
7
8
9
10
11
// this entire ctor must be kept despite the DI, sorry!
public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";
}

public Game1(Foo foo) : this()
{
    _foo = foo;
}

Because we must have a public parameterless constructor to keep XamlGame happy, I’ve moved the default instantiation code into it. We can then have a constructor of our choosing (such as the one taking a Foo in the example above) so long as it calls the default constructor (which I’m doing above with the this() instruction).

If you run the above code you’ll see that the constructor that takes a Foo is called first, which in turn immediately calls the default constructor. It’s not pretty, but unless we’re prepared to get our hands real dirty (per the next blog post in this series!!) then that’s the best you can manage.

Summary

This has been a really long post for what amounts to a very sub-par solution, and that’s ignoring the fact that we’ve only tackled the much easier application initialisation aspect. However, it’s all a learning process and we now have all the pieces in place to enact some real change and improve this solution.

There are a minimum of 3 more articles to this series (one of which I’ve already started writing up). I’ve come up with a fairly dirty way of improving on this DI mechanism that hopefully doesn’t involve needing to make changes to MonoGame itself. Then I’ll explore modifying MonoGame (and hopefully look into pushing some changes back upstream) for Application Initialisation, and a final article on heavily modifying the engine in order to support the scoped update loop injection that I mentioned previously. As you can see, despite the length of this blog post, the journey has but begun…

As an aside, it took me almost a whole year to write the second article in this mini-series, and even then I only picked it up because someone showed interest on Twitter (thanks @Osoy13 for your tweet). If this series interests you please drop me a note below or on twitter - it makes a big difference to my motivation to blog more!

Written on January 6, 2019