These two complementary paradigms, rather than object-orientation, could be the future of game development.
Note: This article is a follow up to the previous article, ‘Why Functional Programming Works for Games’.
When we tell the story of functional programming and data-oriented design in games, we tell a story of trade-offs.
What do you get with Functional Programming in games?
- A declarative programming interface, such as Elm-style.
- Ease in reasoning about program behavior at a high level.
- The complete freedom of an open system.
- A great debugging experience.
And what do you pay to get it?
- Higher performance costs depending on how declarative you want to be.
Conversely, what do you get with Data-Oriented Design?
- Unrivaled speed compared to high-level programming paradigms.
- Ease in reasoning about program performance at a low level.
And what do you pay to get it?
- Be prohibited from using abstractive language features and techniques.
- Difficulty in reasoning about program behavior at a high level.
- A requirement to work in an exclusively closed system.
By looking at these trade-offs, we see that functional programming and data-orientated programming are quite orthogonal. But for games, that’s actually a great thing because orthogonal means complementary!
So how do we know which approach to use when writing games?
It’s all about knowing in what way your game needs to scale. For an example, let’s look at game Entities in the Nu Game Engine –
In Nu, you have 3 tiers of Entity configurations out-of-the-box –
- Optimized (Imperative = true; IgnoreLayer = true; Omnipresent = true)
- Functional (the default configuration)
- Elm-style (use the EntityDispatcher<_, _, _> type)
Depending on the tier of the Entity, different numbers of them can be used before soaking the CPU –
- Optimized => 10,000* On-Screen
- Functional => 2,500* On-Screen
- Elm-Style => 1000* On-Screen
*These numbers are discovered by running Nu’s Metrics project with MULTITHREAD enabled
As you can see, you can get a lot of scalability if you’re willing to dial back some purity and declarativeness. And since most types of Entities won’t need scalability beyond the hundreds, you can stick with Elm-style Entities by default. If you need a bunch of bullets flying around the screen, you can use the default Functional configuration. If you’re going for bullets on the scale of a bullet-hell shooter, you can opt for the Optimized configuration.
What about bleeding edge games where you have certain game elements on screen that number in the multiple 10,000’s? Well, that’s where you have to eschew Nu’s normal path of programming and embrace the soon-to-be-available data-oriented style. This programming path will be provided in a future version of Nu (to enable this, the Transform type was recently separated out from the EntityState type and an Entity-Component-System has been prototyped in C++). Once this is put in place, Nu will enable a fourth tier of scalability –
4. Data-Oriented => Multiple 10,000’s On-Screen
In my view, game engines of the future will use a mixture of functional programming and data-oriented design. Object-orientation will take a back seat to these emerging paradigms, it having only succeeded in giving us the worst of both worlds — insufficient abstraction and inefficient performance.
By using both functional programming and data-oriented design in games, we will aim for the best of both worlds!