Many of the more diligent software developers I know are moving to inversion of control and dependency injection to handle references to objects. Coming from a Flash games perspective I don't know all the ins and outs of AAA studios, so: are these used in the retail game world?
-
1\$\begingroup\$ I think that in that world performance matters above all, since more users will be hit by low performance than programmers by bad code. \$\endgroup\$– Bart van HeukelomCommented Aug 25, 2010 at 9:16
-
\$\begingroup\$ Dependency injection sounds like a component-based system, but would you have an example of how one would une inversion of control? \$\endgroup\$– ADBCommented Dec 27, 2011 at 2:39
-
\$\begingroup\$ As most of this thread seems to be misinformed about what IoC is and what it solves and the OP is not really asking that in the first place I will point here for future visitors: sebaslab.com/ioc-container-unity-part-1 \$\endgroup\$– Boris CallensCommented Mar 24, 2018 at 19:37
-
1\$\begingroup\$ Yes, they do. In fact, Rare had a talk about how they are implementing their testing in Sea of Thieves - youtube.com/watch?v=KmaGxprTUfI&t=1s. Unfortunately they didn't have too much to say about Inversion of Control in Unreal Engine, however, I've written a project that demonstrates how it works: github.com/jimmyt1988/UE-Testing \$\endgroup\$– Jimmyt1988Commented May 23, 2019 at 21:41
-
\$\begingroup\$ One of the LARGEST benefits of Dependency Injection (Rename of Inversion of control - same thing) was with a framework and a huge system, I can easily swap 'my remote API that talks to server' with a mock for testing. Without DI, you have to wire this through tons of layers. Also, when done right if I need instance 1 of class Y way over somewhere else, I no longer need to rework multiple layers of code. <- This probably only makes since to those who have used good DI frameworks though as it is hard to explain and only learned through experience. \$\endgroup\$– Dean HillerCommented Jun 18, 2023 at 6:30
8 Answers
You call it 'diligent software development', I call it 'painful overengineering'. That's not to say that inversion of control is bad - in fact, the basic definition of it is good - but the proliferation of entire frameworks and methods of working to achieve all this is little short of insane, especially combined with the way people are trashing perfectly good and clean interfaces in order to be able to inject interchangeable components that 99% of the time you'll never interchange. It's the sort of thing that could only originate from the Java enterprise environment and I'm glad it doesn't have as much of a foothold elsewhere.
Often the argument is that even if you don't interchange components, you want to be able to test them in isolation with mock objects and the like. However, I'm afraid I will never buy the argument that it's worth bloating and complicating an interface in order to be better able to test it. Testing proves one thing only - that your tests work. Clean and minimal interfaces on the other hand go a long way towards proving that your code works.
So, the short answer: yes, but not in the way you're thinking. Sometimes, when you need interchangeable behaviour, you'll pass in an object to a constructor that dictates part of the new object's behaviour. That's about it.
-
5\$\begingroup\$ +1 - I agree that the Java community seems to have vastly over-complicated what seems to be a very simple idea. \$\endgroup\$ Commented Aug 25, 2010 at 14:25
-
7\$\begingroup\$ The idea isn't necessarily that you'll need to swap out different implementations in production, but that you might want to inject special implementations to facilitate automated testing. In that regard, DI/IoC can certainly be useful in games, but you want to keep it reasonably scoped. You shouldn't need more than a few one-time service resolutions at a high level--you don't want to be resolving services for every little object in the game, and certainly not every frame or anything like that. \$\endgroup\$ Commented Aug 25, 2010 at 14:47
-
18\$\begingroup\$ @Kylotan the concept of a test never tries to prove anything. It tries to demonstrate that the behavior with given inputs is as expected. The more behavior is shown to be correct, the greater your belief that overall function is correct but it's never 100%. The statement that a minimal interface proves anything is ludicrous. \$\endgroup\$ Commented Aug 26, 2010 at 18:32
-
3\$\begingroup\$ A component's code needn't be made more complicated to facilitate testing. And the point isn't necessarily to prove correctness, but to help identify regressions or subtle, unintended changes in behavior. \$\endgroup\$ Commented Aug 27, 2010 at 15:34
-
5\$\begingroup\$ @Alex Schearer: I'm afraid I can't ever accept that needing to use a formalised IoC container system and typically creating additional constructor arguments or factory classes to facilitate testing is, in any way, making the code better factored, and certainly not making it looser coupled. In fact it pretty much tramples on encapsulation, a key aspect of OOP. Instead of relying on TDD to drive their design and hope that a good result emerges, people could just follow SOLID principles in the first place. \$\endgroup\$– KylotanCommented Aug 27, 2010 at 16:17
Strategy Pattern, Composition, Dependency Injection, are all very closely related.
Since the Strategy Pattern is a form of Dependency Injection, if you take a look at engines like Unity for example they are completely based off this principle. Their use of Components(Strategy Pattern) is deeply embedded into their whole engine.
One of the main benefits aside from reuse of components is to avoid the dreaded deep class hierarchies.
Here is an article by Mick West who talks about how he introduced this type of system into the Tony Hawk series of games by Neversoft.
Up until fairly recent years, game programmers have consistently used a deep class hierarchy to represent game entities. The tide is beginning to shift from this use of deep hierarchies to a variety of methods that compose a game entity object as an aggregation of components...
-
2\$\begingroup\$ +1, Evolve Your Hierarchy is a great link which I'd completely forgotten about. The Scott Bilas slides are good too -- correct link for these is ( scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf ). There's one more set of related slides, but I've forgotten where... \$\endgroup\$– leanderCommented Aug 25, 2010 at 18:51
-
\$\begingroup\$ Also the article in Games Gems 5 (I think) by Bjarne Rene. \$\endgroup\$ Commented Aug 25, 2010 at 20:07
There seems to be a lot of confusion about the Inversion of Control (IoC) pattern. A number of people have equated it with the Strategy Pattern or a Component Model, but these comparison don't really capture what IoC is about. IoC is really about how a dependency is obtained. Let me give you an example:
class Game {
void Load() {
this.Sprite.Load(); // loads resource for drawing later
}
}
class Sprite {
void Load() {
FileReader reader = new FileReader("path/to/resource.gif");
// load image from file
}
}
In the above it's clear that Sprite.Load
has a dependency on a FileReader
. When you want to test the method you need the following:
- A file system in place
- A test file to load from the file system
- Ability to trigger common file system errors
The first two are obvious, but if you want to ensure that your error handling works as expected you really need #3, too. In both cases you've potentially slowed your tests down quite a bit as they now need to go to disk and you've likely made your testing environment more complicated.
The goal of IoC is to decouple the use of behavior from its construction. Note how this differs from the Strategy pattern. With the Strategy pattern the goal is to encapsulate a re-usuable chunk of behavior so that you can easily extend it in the future; it has nothing to say about how strategies are constructed.
If we were to rewrite the Sprite.Load
method above we would likely end up with:
class Sprite {
void Load(IReader reader) {
// load image through reader
}
}
Now, we have decoupled the construction of the reader from its use. Therefore, it's possible to swap in a test reader during testing. This means that your testing environment no longer needs a file system, test files, and can easily simulate error events.
Note that I did two things in my rewrite. I created an interface IReader
which encapsulated some behavior -- i.e. implemented the Strategy pattern. In addition, I moved the responsibility for creating the right reader to another class.
Maybe we don't need a new pattern name to describe the above. It strikes me as a mix of the Strategy and Factory patterns (for IoC containers). That being said, I'm not sure on what grounds people are objecting to this pattern as it's clear that it solves a real problem, and, certainly, it's not obvious to me what this has to do with Java.
-
2\$\begingroup\$ Alex: nobody is objecting to passing in already-created objects to drive custom functionality where needed. Everybody does it, just like in your example. The problem is where people are using entire frameworks that exist purely to handle this stuff, and religiously code every aspect of functionality to depend on this functionality. They first add IReader as a parameter to Sprite construction, and then end up needing special SpriteWithFileReaderFactory objects to facilitate this, etc. This attitude does originate from Java and things like the Spring IoC containers, etc. \$\endgroup\$– KylotanCommented Aug 27, 2010 at 16:24
-
4\$\begingroup\$ But we aren't talking about IoC containers -- at least I don't see it in the original post. We're just talking about the pattern itself. You're argument, as best I can tell, is "Because some tools in the wild are bad we should not use the underlying concepts." That strikes me as throwing the baby out with the bath water. Hitting the right balance between simplicity, getting things done, and maintainability/testability is a hard problem likely best treated on a project by project basis. \$\endgroup\$ Commented Aug 27, 2010 at 17:54
-
\$\begingroup\$ I suspect many people are against IoC because it means : \$\endgroup\$ Commented May 4, 2011 at 11:33
I'm writing this at a time when the accepted answer is by a contributor who strongly opposes the concept and I wish to provide a different view:
Dependency injection is indeed not widely used in game development.
In my humble opinion, the reason for this is simply that most game developers are self-taught and know very little about software architecture. Most just wing it or even grow attached to hacks such as the singleton anti-pattern.
Dependency injection is, in games as well, a useful technique that will vastly improve a project's structure:
Reduce inter-system dependencies because these are accessed through interfaces
Result in easier-to-understand code because you'll design interfaces to your systems rather than have them happen by accident
Reduce complexity because each system can be understood on its own and accesses other systems only by intentional interface
Improve testability by allowing modules to be isolated via mocks (stand-in dummies for the actual dependencies)
Better testabilityu allows more tests, thus making it easier to refactor and reuse
The resulting smaller code units are easier to maintain and help uphold the single responsibility principle
You can find IoC add-ons for Unity (i.e. Zenject), Godot and Xenko game engine.
Also relevant is that most games eventually find the need to have some kind of services available throughout the game, whether it's for querying time-of-day or weather in the game world, enumerating save slots or reading the difficulty settings. And most game developers then reinvent the wheel:
Unity developers rely on static variables in DontDestroyOnLoad
classes or a master scene that's always remains loaded (or Zenject).
Godot developers have "AutoLoad" scenes that are always loaded before any other scene. Services that should be available throughout the game can be placed in that scene.
Unreal developers extend the GameInstance class to add game-wide services (and the engine offers some services of its own).
Source: being a full-time software developer (visualuation / real time) for 25 years & moonlighting game developer for 30 years.
-
2\$\begingroup\$ This is a great answer and I wish it was ranked higher. The accepted answer doesn't actually seem to understand what Inversion of Control and Dependency Injection are but spends a large amount of effort trashing something unrelated. \$\endgroup\$– ZeelotCommented Apr 17, 2021 at 23:36
I would say that it is one tool among many, and it is used on occasion. As tenpn said any method that introduces vtables (and generally any extra indirection) can have a performance penalty, but this is something that we should only worry about for low-level code. For high-level structural code that isn't really an issue, and IoC can have positive benefits. Anything to reduce dependencies between classes and make your code more flexible.
-
2\$\begingroup\$ To be precise I'd worry about vtable penalties for any code that's called many many times a frame. This may or may not be low-level. \$\endgroup\$– tenpnCommented Aug 25, 2010 at 11:52
Not in my experience. Which is a shame, as games code needs to be very adaptable, and these approaches would definitely help.
However it must be said that both methods could, in C++, introduce virtual tables where there were none before, bringing with them an appropriate performance hit.
Gotta agree with Kylotan on this one. "Dependency injection" is an ugly Java solution to an ugly Java conceptual flaw, and the only reason anyone's looking at it in other languages at all is because Java's becoming a first language for a lot of people, when it really shouldn't.
Inversion of control, on the other hand, is a useful idea that's been around for a long time, and is very helpful when done right. (Not the Java/Dependency Injection way.) In fact, you probably do it all the time if you're working in a sane programming language. When I first read up on this whole "IOC" thing everyone was buzzing about, I was completely underwhelmed. Inversion of Control is nothing more than passing a function pointer (or method pointer) to a routine or object in order to help customize its behavior.
This is an idea that's been around since the 1950s. It's in the C standard library (qsort comes to mind) and it's all over the place in Delphi and .NET (event handlers/delegates). It enables old code to call new code without needing to recompile the old code, and it gets used all the time in game engines.
-
3\$\begingroup\$ I was also of the "so this is what people are excited about?" when I read about DI, but the fact is that most game programmers could stand to learn about it and about how it would apply to the work that we do. \$\endgroup\$ Commented Aug 26, 2010 at 18:39
I'm not a professionnal game dev, and only tried implementing IoC in C++ once, so this is just speculating.
However, I suspect game developers's would be suspicious about IoC because it means :
1/ designing lots of interfaces and lots of small classes
2/ having pretty much every function call being lately bound
Now, it might be a coincidence, but both tend to be a little bit more complicated and/or problematic for performances in C++ (which is widely used in game, isn't it ?) than in Java , where IoC became widespread (Probably because it was relatively easy to do, help people design saner object hierarchies, and because some believed it could turn writing an a application in Java into writing an application in XML, but that's another debate :P)
I'm interested in the comments if that does not make any sense at all ;)