Dependency Injection in Game Dev
Tags: csharp, dotnet, gamedev
Published: 2020-08-17 04:44:07

I am a huge fan of dependency injection. It is a central theme in [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1), which I use extensively. In recent weeks, I became specifically interested in the dependency injection components themselves and learned how to use them outside of ASP.NET Core. It's remarkably simple to create your own [ServiceCollection](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection?view=dotnet-plat-ext-3.1), populate it with services, and then build a [ServiceProvider](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.serviceprovider?view=dotnet-plat-ext-3.1).

While that's all well and good, web development was the only domain I could think of where dependency injection would help. After all, server applications are long-running programs that need to juggle lots of components and connect everything together. I did not anticipate my interest in dependency injection and my interest in game dev colliding.

Here is an idea of the code powering top level systems in my engine.

```csharp
using (var sqlite3 = LibraryMapper.Map<ISqlite3>(nativeDll, SqliteExtensions.ResolveName))
using (var stb = LibraryMapper.Map<IStb>(nativeDll, StbExtensions.ResolveName))
using (var al = OpenAlLoader.LoadOpenAl())
using (var audio = new AudioRenderer(al))
using (var sdl = SdlLoader.LoadSdl())
using (var windowManager = new WindowManager(sdl))
{
    // ...
}
```

So, not only do these systems need to initialize at the beginning, they need to be cleaned up at the end. From there, the systems begin progressively feeding in subsequent systems: `AudioRenderer` requires `IOpenAl`.

Originally, I was happy with this approach. I like how explicit and straightforward the code is. The order of events is clear. The object lifetimes are clear. What's not to like?

I started adding new subsystems. The first thing I did was begin replacing all calls to `Console.WriteLine` with calls to an instance of `ILogger`. This meant configuring the logging system up front and then injecting the logger into every system that is interested.

```csharp
using var loggerProvider = new ConsoleLoggerProvider(new LocalConsoleLoggerOptions());
var logger = loggerProvider.CreateLogger("default");

using (var audio = new AudioRenderer(al, logger))
using (var windowManager = new WindowManager(sdl, logger))
{
    // ...
}
```

This one example wasn't that bad, but I began extracting internal subsystems and injecting them into the necessary main system because I needed access to those subsystems from other main systems. This outer method became somewhat of a nightmare to read and maintain. There were _many_ local variables feeding into _many_ constructors for various systems. I often had to return to the outer method, add stuff, reorder stuff, and come up with a dozen more variable names.

The nice thing about dependency injection frameworks is that they remove the burden of initializing and disposing services in the correct order. So, I decided to look into replacing all this with dependency injection. I was just going to run a little experiment. If I hated it, I could just revert it.

So, I made a service collection...

```csharp
var services = new ServiceCollection();
```

...and filled it with stuff...

```csharp
services
    .AddLogging(builder =>
    {
        builder.SetMinimumLevel(LogLevel.Trace);
        builder.AddConsole(options =>
        {
            options.IncludeScopes = true;
            options.DisableColors = false;
        });
    })
    .AddSqlite3(nativeDll)
    .AddStb(nativeDll)
    .AddOpenAl()
    .AddSdl2()
    .AddSingleton<AudioRenderer>()
    .AddSingleton<WindowManager>()
    .AddSingleton<Renderer>()
    .AddMetricRepository();
```

...and built a service provider.

```csharp
// Make sure I didn't mess up.
var options = new ServiceProviderOptions
{
    ValidateScopes = true,
    ValidateOnBuild = true
};

using var serviceProvider = serviceCollection.BuildServiceProvider(options);
```

Now, I can pull services out, perform additional initialization, and start the game.

```csharp
var windowManager = serviceProvider.GetRequiredService<WindowManager>();
windowManager.Run();
```

I gotta say: I'm _very_ happy with the result. I was worried about losing the clarity of performing all these operations manually, but I much prefer the agility I'm afforded with this deferred initialization. Now, if I want to pull a subsystem into another system, it's as simple as adding it to the constructor.

```csharp
public ImportantSystem(
    ILogger<ImportantSystem> logger,
    ItemSystem itemSystem,
    CraftingSystem craftingSystem)
```

I do not have to go back to the outer method, call the new constructor, pass the dependencies in, and possibly change the order of initialization. I also gain a new superpower: I can [spawn objects and automatically inject all of its dependencies](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.activatorutilities.createinstance?view=dotnet-plat-ext-3.1#Microsoft_Extensions_DependencyInjection_ActivatorUtilities_CreateInstance__1_System_IServiceProvider_System_Object___).

```csharp
var widget = ActivatorUtilities.CreateInstance<Widget>(serviceProvider);
```

I highly doubt I am the first one to have this idea of using DI in a game engine, but it was satisfying to leverage a new bit of tech and simplify the code. Have you leveraged dependency injection outside of web development before? How did it go?