There’s a bit of a schism in the world of programming. This divide isn’t over a single issue, but instead over a sort of emerging design philosophy that tends to cluster around particular ideas. It’s complex and multifaceted, and you could spend an entire book exploring all the various differences. In the broad strokes people talk about it in terms of being a debate between people who favor object oriented (OO) programming and people who favor data-oriented (DO) programming, but that’s mostly shorthand for a lot of competing ideas.
A couple of years ago I wrote about object-oriented programming, and the criticisms people have with it. Since then, that discussion has grown louder and more complex. What I’m about to outline is a simplification of this ongoing debate.
Yes, there are other programming styles / philosophies besides these two, but let’s ignore them for now because they’re not really relevant to game development.
OO design is a design philosophy that dictates how your code should be structured / conceptualized, while DO design is a set of priorities that says what your code should do. A DO programmer might use OO design patterns to solve their DO problems, so these two camps aren’t necessarily at odds. And of course people participating in the debate are often ignoring the all-important context of the domain they’re working in. A user interface programmer will insist that OO design is the way to go, and an embedded systemsIf you’re working on embedded systems, you’re designing control systems for hardware rather than writing something a user interacts with directly. Think drones and comm satellites. developer will claim that OO is madness and DO design is the only sane way to go, but in truth they’re both using systems that fit their particular job.
For the purposes of this discussion, I’m going to refer to these two sides as Object People and Data People, because the OO and DO acronyms look silly. There’s no way I could read the previous paragraph aloud without snickering.
Here are the ideas and attitudes that Object People cluster around:
- Strong support for Object Oriented coding style. (I mean, obviously.)
- The compiler is your friend.
- Maintainability and readability are everything. Well-written code should be beautiful.
- It’s all about the code.
- If you want to improve your work, you need to better understand design patterns, templates, and coding styles.
While the Data People are more likely to favor these ideas.
- Strong support for the notion that there is no single coding style for all tasks. It all depends on the problem you’re trying to solve.
- You can’t blindly trust the compiler because it won’t always do what you expect.
- Performance and stability are everything. Well-written code should work.
- It’s all about the data and the hardware. Your goal is to manipulate the data with the hardware, not to separate the two with layers of abstractions.
- If you want to improve your work, you need a better understanding of the hardware and the data.
Again, I want to stress that these things aren’t always mutually exclusive. Data People want beautiful code and Object People want code that works. I’m taking this noisy, scattered gradient between these two extreme positions shoving everyone to one end of the spectrum or the other. I’m well aware that you can’t sort all programmers into two buckets like this. However, in the interest of not cluttering this up with additional digressions and qualifying asterisks and teasing out all the nuances of this stuff, I’m going to pretend that we live in a two-bucket world.
An Object Person might say something like:
Object Oriented design allows you to create code that maps to real-world structures and relationships. Your program can have a SpaceMarine object, and everyone reading the code will instantly know what that is. The SpaceMarine can hold an object called Gun, and the Gun object can have properties that can be immediately understood.
This design provides a double benefit. One, it allows the designer to express solutions in a way that matches the problem they’re trying to solve. Two, it allows anyone maintaining the code later to understand the structure of the program based on the objects it uses. Another coder can come in and intuit the existence and purpose of SpaceMarine->Gun->Shoot () without needing to painstakingly scroll through all the code.
To which the Data Person might reply:
The metaphor suggested by your class structure is a lie, and it keeps you from reasoning about what the program is doing. Your SpaceMarine isn’t actually carrying a Gun because in terms of data, there’s no such thing. Your “gun” is a combination of three completely different data structures:
- The shared gun model that gets rendered. There’s only one copy of this and it never changes.
- The collection of behavioral characteristics that define a particular type of firearm: Reload speed, fire rate, kick, accuracy, etc. These attributes might change from one type of gun to the next, but all SpaceMarines carrying the same type of gun will share a common copy of this data.
- A set of data describing what a particular SpaceMarine is doing with a particular gun. This includes what animation is playing, how many bullets are left, the attachment location at the SpaceMarine’s hand, etc.
You’ve taken these three totally different concepts and mashed them together. Your code is promising that SpaceMarines carry guns like in the real world, but the reality is that SpaceMarines technically “share” a single gun. If another coder tries to modify the gun for a particular marine, they’ll be modifying the global gun used by everyone, so the code is actively misleading by pretending to work like the real world.
You’ve mixed the gun location data in with gameplay data, so now the positional data needed for rendering isn’t packed together in memory, which means you don’t have a good way to render all the guns in a single batch. This stupid metaphor is costing processor cycles and preventing you from thinking about what the computer is actually doing when it runs your code.
The Object Person would counter that no, these things aren’t a problem if you do proper Object Oriented design and the system works fine if you know how to do it “right”.
The Data Person will counter that regardless of what the hypothetical “right” way is, this confounding and useless way is how the system is taught.
Then the Object Person will claim The Data Person clearly hasn’t ever bothered to learn the thing they’re trying to critique. The discussion quickly spirals out of control from there. I think you get the idea.
The best example of the Data camp is this talk by developer Mike Acton: CppCon 2014: Mike Acton “Data-Oriented Design and C++”. Acton is opinionated and brusque. By assuming that all programs need to push the hardware for optimal performance, he engages in a lot of the domain tunnel vision I talked about at the start of this series. Having said that, he makes a lot of important points about how being ignorant of the fine-grain details of your data and hardware can lead to huge performance penalties.
I don’t think this is a battle between right and wrong. I think this is a battle over suitability. When you’re trying to transform megabytes of dataIn this case “transforming data” is just high falutin’ coder talk for “make shit explode, die, fall over, reload, or otherwise change”. as a result of interactions between thousands of objects from a dozen different chaotic systems and you have a very narrow window of time to work with, then you need to be very focused on your data. You need to think about what needs to be done to that data, how it’s arranged in memory, how long the transformation will take, and how you’ll present the result to the graphics hardware.
On the other hand, not every program needs this fanatical attention lavished on trivial blocks of memory. The classic example is when you’re building a GUI interface. Slider, buttons, input boxes, and check boxes all lend themselves really well to an object-oriented approach. The program often spends most of its runtime idle, waiting for user input. In these cases, those layers of abstraction can take a lot of the burden off our poor programmer. They don’t need to fuss over data structures or police compiler output to make sure it didn’t do anything destructive. If raw performance isn’t your top priority, then you can focus on making pretty, comfortable, easy-to-maintain, self-documenting code.
Games Programming is Kind of Both
The thing is, a video game exists in both of these realms simultaneously. Sometimes you’re talking to the graphics hardware and you need direct memory access and a complete understanding of what will happen when the code is run. Sometimes you need to pick a random number for bonus headshot damage and you don’t care if you waste a couple of processor cycles on needless precision.
If I’m dealing with the graphics hardware or manipulating thousands of particles, then I want to get close to the metal and write something that looks a lot like raw old-school C. On the other hand if I was handling interface stuff or gameplay mechanics, then I’d want to express myself in a comfortable language like C# where I can leave all the heavy lifting to the compiler.
Vexation #5: Adhering to Dogmatic Design Principles is not Programming.
Java is a good example of a “big idea” language. It’s specifically designed to support and enforce object oriented design patterns. You can’t just make some code to play a sound. Playing a sound is an action, and so you need that action to be owned by an object. Instead of PlaySound (), you create a SoundPlayer class, you create an instance of that class, and then you order it to do what you want in the form of SoundPlayer.Play ().
This problem where actions are owned by objects leads to the somewhat absurd Kingdom of the Nouns. There’s nothing inherently wrong with this style of coding, and I’ve run into many situations where this style felt really good. But in Java, you’re not allowed to deviate from it. You’re forced to use object oriented design, even if that design doesn’t suit your project. You have to figure out how to express this verb / action using a noun / object design. You end up writing containers for actions and creating all these extra classesNot to mention the source files to hold them. and bloating code just to adhere to object-oriented orthodoxy.
Likewise, Haskel is a “big idea” language. It’s built around the idea of functional programming. Functional programming is designed to solve the problem of cascading state changes that I talked about back in part 2. Consider a line of code like this:
That seems trivial enough. But then you run the program and encounter a situation where shooting the gun created a bullet that blew up a red barrel that created a spherical damage zone that generated particles and sounds and damaged a support pillar that triggered a physics event. That one line of code makes massive changes to the state of the world. These changes are varied, numerous, and extensiveIn the real world, you’d have some sort of event-based system that would wait until the gun was done being fired before kicking off any of those other systems. But explaining how that works would take a lot of page space, and this example is good enough for illustrating the problems of expanding state change..
Functional programming doesn’t allow objects in the program to make changes to each other. A bullet can’t damage a bad guy. Instead, there needs to be some sort of master object that owns all the bullets and bad guys. When a collision happens, the owner says to the bad guy, “Here’s a bullet. Shoot yourself with it and return a version of yourself exhibiting the result.” So then the owner throws away the old version of the bad guy and replaces it with the version that’s been shot.
This means you’ll never have any surprise side-effects and things will theoretically be easier to debug. However, this style of programming is very tricky and has a large performance overheadI imagine the need to create a “new” SpaceMarine every frame to replace the one from last frame means there will be a lot of churn in memory allocations. But I’ve never done this sort of coding so I can’t say for sure..
I want to stress that Java and Haskell are not bad languages because of these restrictions. These languages were designed around these ideas on purpose, and faulting the languages for sticking to their core design is somewhat missing the point. It’s like complaining that the low blade clearance on a band saw makes it tough to cut bread. There’s nothing wrong with the tool, it’s just not the tool you should be using for this job.
(Of course, then you come to the problem where your workplace forces you to use the wrong tool. That sucks, but that’s a problem with people and not the language.)
Having said all that, strict object-oriented design is a bad fit for gamesAlthough informal OO design is fine. Objects are fine, as long as you’re allowed to deviate from OO when doing low-level stuff., and functional programming even moreso. I know I’ve spent a lot of time in this series fantasizing about what a modernized language for GameDev might look like, but the limitations of hardware mean we’re still bound by the Old Ways. We’re still going to need occasional direct memory access and unguarded access to memory. A GameDev language needs to be forward-looking, but it also needs to hang onto the 1972 way of doing things.
 If you’re working on embedded systems, you’re designing control systems for hardware rather than writing something a user interacts with directly. Think drones and comm satellites.
 In this case “transforming data” is just high falutin’ coder talk for “make shit explode, die, fall over, reload, or otherwise change”.
 Not to mention the source files to hold them.
 In the real world, you’d have some sort of event-based system that would wait until the gun was done being fired before kicking off any of those other systems. But explaining how that works would take a lot of page space, and this example is good enough for illustrating the problems of expanding state change.
 I imagine the need to create a “new” SpaceMarine every frame to replace the one from last frame means there will be a lot of churn in memory allocations. But I’ve never done this sort of coding so I can’t say for sure.
 Although informal OO design is fine. Objects are fine, as long as you’re allowed to deviate from OO when doing low-level stuff.
A Telltale Autopsy
What lessons can we learn from the abrupt demise of this once-impressive games studio?
Raytracing is coming. Slowly. Eventually. What is it and what will it mean for game development?
The Strange Evolution of OpenGL
Sometimes software is engineered. Sometimes it grows organically. And sometimes it's thrown together seemingly at random over two decades.
The game was a dud, and I'm convinced a big part of that is due to the way the game leaned into its story. Its terrible, cringe-inducing story.
TitleWhat’s Inside Skinner’s Box?
What is a skinner box, how does it interact with neurotransmitters, and what does it have to do with shooting people in the face for rare loot?