A Component-Based System That Isn’t Awful

While bringing Ladon’s code back from the dead, I decided to fix something that has been bugging me from the start.  In fact, it has bugged me in pretty much every OOP project I’ve ever done.  C++ and OOP in general were a revolution in programming that built on top of the traditional “procedural” paradigm.  Instead of endless lines of procedural code that look like this:

Do this!
Do that!
Do this other thing!

… programmers could now create objects.  You could define a “car” and a “plane” and a “human” and give them meaningful relationships like “a car is a vehicle” and “a plane is a vehicle,” but a human is not.  It might seem obvious that you’d need some kind of object system to do anything useful, but it really wasn’t possible in the early days.

So, OOP came along and gave us the ability to organize code into a hierarchy.

An F-16 is a fighter jet.
A fighter jet is a plane.
A plane is a vehicle.
A vehicle is an entity.

The huge gain here is code reuse.  Your game (or whatever) might have a dozen different types of fighter jets and a dozen other types of planes and a hundred types of vehicles, including cars, motorcycles, etc.  With a hierarchy like this, though, you only have to write the code that’s common to all vehicles once.  For example, the player can probably do things like “enter” and “drive” anything that’s a vehicle.  So you write the code for “enter_vehicle” and “drive_vehicle” once and all vehicles can now do that.

[note – I’m using the term “object” a lot here and programmers will point out that “class” is more correct in some situations.  The difference between the two is beyond the scope of this article!]

Beyond that, you specialize each vehicle as much as necessary.  Driving an F-16 is not the same as driving a Volkswagen, but they do have a lot in common.  Put what’s common to both into the “vehicle” object and put what’s unique into each individual object.  This gets you a ton of mileage compared to plain old procedural code.

Unfortunately, these object hierarchies almost always run into a massive roadblock.  Suppose you’ve carefully laid out this giant tree of object types and you’ve decided that you need “things that shoot” to be in there somewhere.  Seems logical if you’re making a game and everyone’s going to be shooting at each other.  So, your “tank” objects will definitely be “things that shoot” and so will your “fighter jet” objects.  But now you have a dilemma because the parent of “fighter jet” was already “plane.”  But you can’t make “things that shoot” the parent of “plane,” because not all planes shoot.  And you can’t make “plane” the parent of “things that shoot” because not everything that shoots is a plane!

Fairly early in its development, C++ tried to solve this with something called “multiple inheritance.”  The idea was that objects could have multiple parents.  Your “F-16” could be both a “plane” and a “thing that shoots” and anything else you want it to share code with.  Seems natural.  Sadly, multiple inheritance died a quick death.  It caused more problems than it solved.  A few C++ programmers still use it, but they are gluttons for punishment.

Something better did eventually come along.  Instead of inheritance, the “composition” approach became popular.  With composition, you define the pieces of a thing.  Instead of saying “an X is a Y,” you say “an X has a Y.”  So, your plane has “wings” and “thrusters” and so on, while your car has “wheels” and “doors.”  This is fantastic because you only have to define what a “wheel” is once and then every car can use that component.  Better yet, components can be as abstract as you want.  So you make a component like “flyable” and every plane and helicopter can use that component, while every car and motorcycle gets a “driveable” component.  You get just as much (or more) code reuse as you did with inheritance without the nastiness of restricting everything to a parent/child relationship.

Of course, nothing is perfect, and component-based systems have a few drawbacks.  They’re not showstoppers, so most programmers apparently just live with them.  The most important is a big loss of efficiency due to memory allocations.  In any type of language, creating a new object means that you have to allocate some memory for it.  This is one of the most expensive – slow – things you can do in programming.  The reasons are long and gory, so just take my word for it.

The number of allocations you have to do increases drastically with a component system.  With simple inheritance, you only had to allocate your “plane” or “car” in one shot.  With components, you have to allocate the car, each wheel, each door, the “driveable” component, and every other component a car requires.  So your game is about an order of magnitude slower!  Not cool.  You’re forced to choose between the trusty efficiency of inheritance or the sexier but slower convenience of composition.

There are some “fringe” efforts to fix this problem.  I’ve watched some very recent university-level lectures that propose some complicated ways to deal with this.  I have not seen one that I felt confident about trying.

After much thought and experimentation, I came up with my own solution.  I’m a huge fan of a technique called “meta programming.”  The gist of this approach is writing a program that writes a program.  If you can wrap your brain around it, it can solve a lot of ugly problems for you.  It can also probably drive you completely insane in large doses.

Basically, what I did was write a script that runs every time I build Ladon.  Just before the build begins, it reads through my code and finds anything that looks like a component, along with any objects that use that component.  It then replaces the allocation of that component with a static declaration.  The change is really subtle, but the upshot is that when you allocate your car object, you also get the wheels, doors, and everything else all in the same allocation.

The real reason I had to turn to meta programming to make this all work is that C++ simply doesn’t support what I wanted to do without some help.  Without getting too bogged down in the details, C++ can do components and C++ can do static members, but those two are mutually exclusive.  A “door” can’t be both a component and a static member without some really nasty hacking.  So I did the nasty hacking and hated it, then found a way to have my meta program do the nasty hacking for me!  So all the ugliness is still there, but I never have to see it or worry about it because it happens quietly in that pre-build step when my script runs.

The result is that I just write code as if I were doing a component-based system just like everyone else does, but it is quietly massaged into something less awful behind the scenes.  It’s a problem I’d wanted to solve for many years and I’m really happy with how it worked out.

If anyone’s interested in the nitty-gritty details of how it works, leave a comment here or find me on Facebook.  I’m happy to share the script.

Leave a Reply

Your email address will not be published.