Historically, I’ve always gravitated towards an inheritance-based approach when it comes to developing a framework to work with. To me, it makes sense, given how everyone was taught the principles of object-oriented design through various classes. In addition, having worked with the Unreal Engine during my stint at LucasArts (which is inheritance-based), I feel I am quite familiar with the things a “traditional deep hierarchy” has to offer. On a side note, I am also particularly drawn to how Unreal manages their game objects for some reason, although I suspect I might be giving that system more credit compared to others.
However, I’ve also heard a lot of good things about component-based architecture over the years. But why abandon what is familiar to me in favour of this newfangled paradigm?
The problems with “traditional deep hierarchies”
The problems with a hierarchical structure arises in development. As the game develops and changes, the hierarchy is frequently modified. Furthermore, we usually need specific sorts of functionality to the individual game objects, which means that the objects must either fully encapsulate that functionality themselves, or derive from an object that already has that functionality.
Sometimes, this functionality is added near the root of the inheritance tree, which means that all derived classes also have this functionality, but this also introduces a lot of bloating and its associated overhead. Leaf nodes hold a lot of functionality from its ancestors, but it’s not necessarily clear that the leaf actually needs all of its ancestor’s functionalities. Eventually, more and more functions get aggregated into a single, monolithic class (typically the player), which ends up as one gigantic class containing a lot of complex, tightly coupled functionalities. As the class gets larger and larger, seemingly trivial changes can have far-reaching, unforseen implications, so to a new programmer on the team, maintaining such code is like walking through a minefield.
I can say this from personal experience– one of my first tasks after joining LucasArts was to add a trivial piece of functionality to a game mechanic. All it took was one boolean and one function. I did not anticipate that that trivial addition would invalidate a whole week’s worth of telemetry results because of how it affected a completely different system!
So the alternative it to then add the functionality towards the leaves. As a result, we avoid this sort of code bloat, but now the functionality is compartmentalized, and only objects that are specifically programmed to have that particular functionality can use it. If we have another class that requires the same functionality, we’d have to duplicate that code across both classes. Alternatively, we restructure the class hierarchy to move and combine functionality in the “correct” places, but that then leads to a lot of messy refactoring of code.
Object-oriented programming is the problem!
At the core, perhaps the approach is wrong. I still believe that there’s something innately beautiful with the whole concept of object-oriented design, but there are far too many problems out there that exist on a systems-level as opposed to an object-level.
For example, let’s say you were writing a graphics engine. The object-oriented approach would have each game object contain some Draw() method, which will then draw the object to some render target. Following that paradigm, rendering a scene would be simply iterating over a container, calling each object’s Draw(). Each game object would then set the appropriate render state and draw itself, and the framerate plummets to single digits.
With no overarching management (in this case a scene graph), resources are thrashed and operations are performed in a manner that is highly inefficient, similar to how a panicked crowd cannot leave a burning cinema quickly. In the case of the graphics engine, the object-oriented approach thrashes the bus between CPU and GPU, as each object sets its own individual render state. The objects cannot assume that certain parts of the GPU’s render state have already been set up, so must perform everything from scratch, resulting in wasted effort from duplicate operations.
Proper management of the render state requires a macro-view of the objects involved to reduce the number of changes to the render state, thus reducing the data transfer between CPU and GPU, but this management cannot code at the object-level!
Moving on: Entity-systems vs components
If splitting up a hierarchy into component objects is one level of abstraction, further subdividing the components into the methods and the data would be the next step. But why do that?
The problem is that since components as a collective represent part of a single object, there will undoubtedly be some dependencies between components. For example, the RenderableComponent knows how to draw itself, but does not know where. Of course, you could then put the object’s position in the RenderableComponent, but then the PhysicsComponent will also need to update that value. To avoid this dependency, we can move the position into the GameObject class, but now we’re taking a step back towards the monolithic class we were trying to avoid in the first place.
Once components start relying on one another, we have an additional problem– systems invariably change, and design is always fluid. If many months later down the road, we decide to change how one component behaves, we also break all the other components that rely on the one we just changed.
The solution is to remove all logic from the components, leaving behind just data. The logic is then put into a new class, a System. As a result, the implementation is now independent of the data. The idea is then that components need not know anything about each other- they meet just one task, and should only handle that one task. If data from another component is required (like position), then it is the System that queries another System for the relevant data.
As a result, I’ve decided to go ahead with the Entity-Systems approach for the following reasons:
1) To learn about component-based architecture and Entity-Systems architecture
Despite writing (and rewriting) several game engine frameworks over the last few years, I have yet to actually get around to building a component-based engine. I’ve heard many good things about component-based architecture though, and I figure it’s about high time I explore other approaches to game engine development. The last time I tried was two years ago, and that failed miserably, mostly due to not being familiar with the paradigm.
Now I’m (theoretically) more experienced compared to two years ago, I should be better prepared to take on the problem.
Just to reiterate- my project is to write a fluid simulator via smooth particle hydrodynamics. I’ve done a little bit of reading, but as with all other things, the devil is in the details, and I have absolutely no idea what kinds of pitfalls lie ahead.
As a result, it would be no surprise if the code base changes constantly over the course of trying to implement my fluid simulator, so no matter whatever framework I work with, it must be flexible.
3) Entity-Systems and RDBMS‘es
The notion of an Entity-System architecture resembles a relational database management system (RDBMS), which I just covered last semester in CS315. I think it’d be interesting to see how database concepts apply here.
4) Parallel Processing
Because each System is self-contained (generally, anyway), this code structure lends itself very nicely to parallel processing, both in terms of data parallelism (individual components aren’t shared between threads) and task parallelism (a System can be executed independently on a separate thread). I am currently taking a course precisely in parallel processing using the GPU (CS397), so am curious to see how the concepts in that class apply to this project. Also, due to the calculation-intense nature of fluid dynamics, I’m sure I am going to have to fully utilize the GPU for my computations if the framerate is to be preserved.
Here are some resources that got me looking into this architecture:
This article by Robert Nystrom was the one that first got me into looking into component-based architecture. I tried implementing some of the concepts presented into my game, Blockhead, and was pleased with the results, even if I broke a number of rules in doing so.
Entity Systems are the future of MMOG development – Part 1
Entity Systems are the future of MMOG development – Part 2
Entity Systems are the future of MMOG development – Part 3
Entity Systems are the future of MMOG development – Part 4
Entity Systems are the future of MMOG development – Part 5
This series of articles by Adam Martin from 2007 seems to be one of the most referenced series of articles regarding Entity-Systems architecture.