Dependency Injection for Games
Tags: gamedev, architecture, cpp, tutorial
Published: 2023-09-11 11:49:59


Whether you are a **game programmer** or a **game engine developer**, a well-organized architecture is crucial to keep your project maintainable, readable, and safe— especially if you work with a team on a large codebase.

This article aims to present the concept of **Dependency Injection**, and to show how it can help you to easily structure your **game architecture** in a robust and flexible way, without any performance overhead.

Using a dummy game application as a **practical example**, we will start by defining the problem we intend to solve. We will try different strategies, and demonstrate how Dependency Injection emerges as the ideal solution.

We will use **C++**, the go-to language for high-performance applications, but the ideas discussed are universal, and applicable in other languages.

The code presented in this article is available on [**GitHub**](https://github.com/Ceffa93/dependency_injection_for_games).

## Problem Definition

Games are complex applications, consisting of multiple unique systems, such as Rendering or Physics. In a typical game application, systems are initialized on startup, they interact with each other for the lifetime of the application, and are destroyed when the application terminates.

Some systems are specific to the game being developed, while others are more general, and are typically wrapped into a **game engine**:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69oeromapt1g1yj72j9a.jpg)

In our discussion we will not consider this separation, and refer to any system in the application as a **game system**.

Let’s now introduce the example that will accompany us for the rest of this article — a dummy 2D game application where two red circles bounce around the screen. Our game consists of just a handful of systems:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fjc5gds55wsdo6q55dpt.jpg)

* `State` holds the state of the game: the circle positions and velocities.

* `Simulation` moves circles in the `update()` method, modifying `State`.

* `Physics` is used by `Simulation` in `update()` to resolve circle collisions.

* `Rendering` reads `State`, and draws circles on screen with `render()`.

* `OpenGL` wraps the OpenGL API, required by `Rendering` to `render()`.

* `Loop` runs the game loop, which calls `update()` and then `render()`.

Let’s identify the **dependencies** between our systems:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3rgilpclp6cfjzvluzl6.jpg)

The design of our application is nicely represented by a graph, where nodes represent systems, and edges represent dependencies. In particular, this is a **directed acyclic graph**, where there is no circular dependency.

Now that we have a solid theoretical understanding of how to organize our architecture, it is time to introduce the concrete problem at the core of this article: **how do we turn this theory into practice?**

We wish to find a way to **accurately** express our theoretical architecture in code, with a focus on **clarity**, **safety**, **modularity** and **performance**.

Instead of trying to derive our solution from these high-level goals, in the next section we will proceed bottom-up, and **dive into implementation**. As we try different strategies, we will get a practical understanding of what problems we wish to avoid, and we will define rules that we wish to follow.

Eventually, we will converge on an ideal solution: **Dependency Injection**.

---
>  Further reading:
For simplicity, we ignore the situation where systems implement and depend on interfaces, quite common in real game applications. If you are interested to know more, refer to the appendix on [**Dependency Inversion**](https://dev.to/filippoceffa/dependency-injection-for-games-appendix-dependency-inversion-3coc) after finishing this article.


## Problem Solution

### Attempt 1: Systems are global variables

Based on the problem definition, we know that game systems must be unique, created at startup, and destroyed at shutdown. These requirements are satisfied by creating a global variable for each system:

```c++
// State.h
class State{...};     // class definition
extern State g_state; // global variable declaration

// State.cpp
State g_state{};      // global variable definition
```



Systems access dependencies directly from global state:
```c++
#include <State.h>
Simulation::Simulation()
{
    g_state.read();
}
```

One problem with global variables is that we do not control their construction order. If `g_simulation` happens to be constructed before `g_state`, the above code causes an uninitialized memory access.

We wish to have a way to guarantee that `g_simulation` is constructed after `g_state`. And, to avoid the same issue in the destructor, we also wish to guarantee that `g_simulation` is destroyed before `g_state`.

Let’s formalize our wish in a rule:
>  Rule 1: Systems must be created after / destroyed before dependencies.

Another problem with global variables is that their access is not limited to constructors — they can be accessed from anywhere:
```c++
void Simulation::update() 
{ 
    g_state.write();
}
```

This freedom has a side effect — it becomes difficult to understand the dependencies of a system, because they are not listed in a single place.

Unclear dependencies make it hard to understand the architecture, to refactor, and to make changes. Let’s define a rule to prevent this situation:
>  Rule 2: Dependencies must be explicit.

The final issue with global variables is the potential to easily introduce **circular dependencies** between systems:
```c++
void Physics::foo() 
{
    g_rendering.bar();
}

void Rendering::foo()
{
    g_physics.bar();
}
```
To ensure that our codebase remains **modular** and **maintainable**, we wish to forbid circular dependencies altogether:
>  Rule 3: Introducing circular dependencies must be prevented.

### Attempt 2: Systems own dependencies

Let’s make systems responsible for managing their own dependencies. 
The dependencies are member variables, created in the constructor:
```c++
 Loop::Loop()
 : m_simulation{} // dependency created here
 , m_rendering{}
 {}
 
Loop::~Loop(){} // dependency destroyed here
```

Using constructor recursion, a system creates its entire dependency subgraph. Consequently, constructing the root system `Loop` triggers the creation of every system.

We construct our application by creating `Loop` on the stack of `main()`:
```c++
 int main()
 {
     Loop loop{}; // recursively construct every system
 }
```
A problem arises when multiple systems, like `Simulation` and `Rendering`, depend on the same system, such as `State`. Both `Simulation` and `Rendering` create separate copies of `State`, but systems must be **unique**.

To resolve this, we can create `State` within `Loop`, the parent system of `Simulation` and `Rendering`, and have `Loop` share `State` with their constructors by reference:
```c++
 Loop::Loop()
 : m_state{} // create here
 , m_simulation{m_state} // share reference
 , m_rendering{m_state} // share reference
 {}
```

This solution patches the problem, but it introduces an extra dependency. We have failed to exactly map our theoretical architecture into code:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kf1c5vj2yv30lr697we5.jpg)

There is a fundamental problem with this approach that prevents us from converging on an ideal solution. In order to understand it, we should take a step back, and start thinking in terms of **responsibilities.**

Systems are burdened with **two** responsibilities:

 1. **Functionality:** executing the logic that defines system behavior.

 2. **Structure**: creating, destroying, organizing, passing dependencies.

The dependency graph should only reflect dependencies that occur due to **functionality**, such as `Simulation` depending on `Physics` for collision resolution. However, the need for organizing the application **structure** introduces additional dependencies, such as `Loop` depending on `State`.

To resolve this problem, we wish to follow the **single responsibility principle**, and give systems a single responsibility: **functionality**.
>  Rule 4: Systems must not be responsible to structure the application.

### Successful attempt: Systems receive dependencies

In this final attempt, the responsibility for structuring the application is moved to `main()`, where every system is created in the correct order, and passed as dependency to other systems:
```c++
 int main()
 {
     State state{};
     Physics physics{};
     Simulation simulation {state, physics};
     OpenGL openGL{};
     Rendering rendering{state, openGL};
     Loop loop{simulation, rendering};  
 } // out of scope: system destructed in reverse order
```
Systems are no longer responsible for structuring the application. They receive their dependencies through the **constructor**:
```c++
 Loop::Loop(Simulation& simulationRef, Rendering& renderingRef)
 , m_simulationRef{simulationRef}
 , m_renderingRef{renderingRef}
 {}
```

This approach follows all our rules:
>  Rule 1: Systems must be created after / destroyed before dependencies.

✅ System is outlived by its dependency, as it is created later on the stack.
>  Rule 2: Dependencies must be explicit.

✅ The signature of the system constructor lists all dependencies.
>  Rule 3: Introducing circular dependencies must be prevented.

✅ It is impossible to order system creation to cause a cycle.
>  Rule 4: Systems must not be responsible to structure the application.

✅ Application structure is entirely determined in main().

Success!

We converged on a solution that precisely implements our theoretical architecture, while being simple, safe, modular and efficient.

As promised, this ideal solution is none other than an implementation of **Dependency Injection**.
>  Dependency Injection is a programming technique in which an object [..] receives other objects [..] that it depends on.
(Wikipedia)

---
>  Further reading:
In applications with a large number of systems, the cost to write and modify the code in `main()` is significant. If you wish to learn of an automated solution to reduce the boilerplate code, refer to the appendix on [**Injection Container**](https://dev.to/filippoceffa/dependency-injection-for-games-appendix-injection-container-5ebd).

## Conclusions

I hope this article was able to make you excited about Dependency Injection, and its application in game development.

By adopting Dependency Injection, you will quickly realize how radically it improves your everyday game development experience:

* You will easily understand, reason and discuss your architecture.

* You will quickly refactor and make changes with peace of mind.

* You will never worry about accessing uninitialized dependencies.

I suggest that you try adopting Dependency Injection in your own project, and I believe you will not be able to look back!

## Next steps

* Check out the accompanying [**GitHub repository**](https://github.com/Ceffa93/dependency_injection_for_games), which contains an **implementation** of the dummy game application.

* Read the appendices on [**Dependency Inversion**](https://dev.to/filippoceffa/dependency-injection-for-games-appendix-dependency-inversion-3coc) and [**Injection Container**](https://dev.to/filippoceffa/dependency-injection-for-games-appendix-injection-container-5ebd).

* If you have any questions, or wish to share your thoughts, please feel free to leave a **comment**, or to contact me on [**LinkedIn**](https://www.linkedin.com/in/filippoceffa/).