Is the Component Object Model Really Worth it?

Started by
21 comments, last by Codeka 15 years, 3 months ago
Quote:Original post by Jetjayjay
So Antheus, what you mean is that the dependencies between implementations can be reduced by offering some sort of null or default object if no such component is found in the entity?
That's an implementation detail that, in this particular case, avoid an if statement in component's main body (the part which gets called once a frame).

Quote:That would probably solve many of the problems with all of the coupling between components
This particular example doesn't change coupling in slightest. It's error handler, which allows that particular renderer to be resilient to the case where a certain component doesn't have Health. Usually, one would want to log such an error.

Quote:RTTI is pretty performance-intensive
Does your profiler agree?
Quote:and all of the dereferncing stuff is also fairly slow
How so? Component based design can greatly improve cache efficiency, since like data is located close to like data. For example, Health component above can be allocated in std::vector<Health>, and if it's min/max/current (12 bytes) allocated much more compactly than if it were part of every object. So if you need to iterate over all items with health, you iterate over that vector, not needing to check whether an Entity has or has not health.

In a game with 10,000 objects, but only 15 killable items, that means you iterate over 15 vector elements or 180 bytes. Compare this to running a query over 10,000 polymorphic objects and doing RTTI queries on them to see if they are of KillableItem type, where each object has 1k memory footprint.

Or, alternatively, two component instances can be bound on 1-for-1 basis via some notification mechanism, so that instance of Foo component listens to Power property/component of Bar component.

In OO model it's possible to maintain separate array for KillableItem, but that requires you to re-invent component system managers for OO system and defeats the purpose of inheritance.

Quote:The semantic programming involved in a component object system defeats the purpose of the system itself, which was to loosen the coupling between components of an objects.
This isn't correct.

Coupling between interfaces is desirable, since it enforces relations. Hence, ideal coupling is that which fully expresses relationship using minimal number of participants.

In above component example, coupling meets this criteria. There are two objects, Health<-Component and HumanRenderer<-BasicRenderer<-Component, coupled to each other in absolute oblivion. And BasicRenderer is inherited for implementation, not interface. Later, one might want to factor out rendering functionality into separate class, which could be owned by any rendering component.

If this were OO system, HumanRenderer would be coupled to:
MainCharacter<-PlayerCharacter<-KillableCharacter<-Character<-Object                                       <-InventoryHolder<-Container<-Object                                       <-WeaponSlots<-Container<-Object                                       <-Renderable<-GraphicObject<-Object
Whoa... So suddenly our tiny little renderer is coupled to dozens of interfaces - instead of on one single value.

Which of the two systems is more coupled?

Quote:Not pretty, and has plenty of performance overhead.
But few ever mention this particular overhead. And when they do, they solve it using the approach I presented - by caching dependencies, or values themselves locally.

Quote:By the way, I looked at the way Dungeon Siege implemented a component object system. The way the did it, though, isn't really a true composite pattern.
Design patterns have surprisingly little to do with design itself. Design patters aren't really design patterns, they are example implementation that were created to cope with many conceptual deficiencies of C++. So GoF bible emerged, providing nice implementation snippets of concepts that have been integral part of Lisp for decades.

But since they are defined within the scope of GoF's own implementation, they are defined by implementation through which they are presented.

For example, EnumDisplayDevices is, at design level, composite pattern. But since it doesn't have UML diagram, rich object hierarchy, inheritance, etc.... it takes a bit of paradigm shift to understand why.

And to this extent, thinking of design in terms of patterns is counter-productive and overly limiting. They don't help solve or define the problems. They are copy paste code snippets.

Quote:What they had was a built-in template system, not generic, loosely coupled components in an object. It was really more of a "fill in the object data fields" type of functionality.


Yes, this is what such system ends up as. Completely data driven, where designers work with simple text files, but get full access to all programming aspects, without ever seeing the code. This in turn lowers the cost of asset creation.

Quote:but I'm still not convinced that component-entity programming is worth the trouble you get in return.


That's the other way around. Component based architectures are used to solve a certain problem. In this particular case, loose coupling and data driven approach.

But that may or may not be what you need. If you're going for a walk through the park, using a car is the wrong choice. But that doesn't make car worth less.

Maybe you're trying to fit all of this too much into GoF. At present, component architectures are not formalized enough to provide a small set of functionality in form of patterns.
Advertisement
In our engine we don't use RTTI.

We have every 'entity' as Antheus calls them store a heap sorted key indexed table of components. You request components by symbolic names i.e.

CHealthComponent* health = GetComponent(Symbol("health"));

If we know we wont lose the health component over the course of the objects lifetime we will cache the pointer other wise we'll store a target (smart pointer) that will if check the validity of the health component at the start of the frame.

Performance has never been an issue with the component architecture - we've always had to worry about performance in other area's of the game. We shipped the game 'god of war: chains of olympus' with this system which had to run on the psp - a very limited console in terms of performance. For our team the performance was never an issue.

For your original issue post on render components I would have designed it something like Antheus suggested. The Health component could flag itself as a renderable object and it has a render type function it can install (either via. a component or a function pointer). The render function would issue render commands to the system. The health component knows that it needs to render itself in a certain way and the rendering system never needs to know about "health" components or any gameplay specific feature.

-= Dave
Graphics Programmer - Ready At Dawn Studios
Quote:Original post by Antheus
Quote:Original post by Jetjayjay
So Antheus, what you mean is that the dependencies between implementations can be reduced by offering some sort of null or default object if no such component is found in the entity?
That's an implementation detail that, in this particular case, avoid an if statement in component's main body (the part which gets called once a frame).

Quote:That would probably solve many of the problems with all of the coupling between components
This particular example doesn't change coupling in slightest. It's error handler, which allows that particular renderer to be resilient to the case where a certain component doesn't have Health. Usually, one would want to log such an error.


My bad. I meant dependency between components, not coupling.

Quote:
Quote:RTTI is pretty performance-intensive
Does your profiler agree?


If every component has its own std::string of its type, like "RenderComponent", there will be a whole lot of std::strcmp in your code, and comparing strings is not particularly efficient. (Alternatively, you could use integer values as IDs, but this would make your code incredibly hard to read.) But the real problem is when you do this several thousand times per step. Sending a message would mean querying an entity for a certain component. Doing this for every message sent will not be without a performance cost.

Quote:
Quote:and all of the dereferncing stuff is also fairly slow
How so?


I literally mean the derefencing operator. (Foo.Component or Foo->Component) Component-entity modeling tends to use the dereferencing operator more than inheritance/polymorphism, and dereferencing does have a performance cost. It may seem trivial at first, but when you see the sheer number of times you will do it per frame, it will definitely be noticeable.

Quote:Component based design can greatly improve cache efficiency, since like data is located close to like data. For example, Health component above can be allocated in std::vector<Health>, and if it's min/max/current (12 bytes) allocated much more compactly than if it were part of every object. So if you need to iterate over all items with health, you iterate over that vector, not needing to check whether an Entity has or has not health.

In a game with 10,000 objects, but only 15 killable items, that means you iterate over 15 vector elements or 180 bytes. Compare this to running a query over 10,000 polymorphic objects and doing RTTI queries on them to see if they are of KillableItem type, where each object has 1k memory footprint.

Or, alternatively, two component instances can be bound on 1-for-1 basis via some notification mechanism, so that instance of Foo component listens to Power property/component of Bar component.

In OO model it's possible to maintain separate array for KillableItem, but that requires you to re-invent component system managers for OO system and defeats the purpose of inheritance.


Implementing this kind of manager would be so trivial (just adding a container of object types and changing the entity factory a little bit) that I don't think it would really be reinventing the wheel. And by the way, why would that system defeat the purpose of inheritance? Inheritace is for common functionality that needs to be expanded upon. That isn't affected by using object managers.

A manager like this would make the line between component-entity programming and inheritance/polymorphism rather fuzzy. With this kind of manager, really, the only difference between the two would be what the individual programmer prefers.

Quote:
Quote:The semantic programming involved in a component object system defeats the purpose of the system itself, which was to loosen the coupling between components of an objects.
This isn't correct.

Coupling between interfaces is desirable, since it enforces relations. Hence, ideal coupling is that which fully expresses relationship using minimal number of participants.


Not much I can say to this. I guess we are trying to use the system for different purposes, if coupling is what you want for you objects and it is what I want to avoid.

Quote:
Yes, this is what such system ends up as. Completely data driven, where designers work with simple text files, but get full access to all programming aspects, without ever seeing the code. This in turn lowers the cost of asset creation.


When the system is completely data driven, it doesn't really make a difference if you use the component-object system or inheritance/polymorphism.

Quote:
That's the other way around. Component based architectures are used to solve a certain problem. In this particular case, loose coupling and data driven approach.

But that may or may not be what you need. If you're going for a walk through the park, using a car is the wrong choice. But that doesn't make car worth less.


Agreed. As I said above, we are probably using the system for different purposes, so what you are saying is very true.

Quote:
Maybe you're trying to fit all of this too much into GoF.


That is probably true. :)

Quote:
For your original issue post on render components I would have designed it something like Antheus suggested. The Health component could flag itself as a renderable object and it has a render type function it can install (either via. a component or a function pointer). The render function would issue render commands to the system. The health component knows that it needs to render itself in a certain way and the rendering system never needs to know about "health" components or any gameplay specific feature.


David Neubelt - like I said above, once you introduce such a system, whether you use normal inheritance and polymorphism or component-entity modeling is rather irrelivant.



By the way, does anyone know what the ratio between inheritance/polymorphism based games and component-object modeled games is?





Quote:Original post by Jetjayjay

My bad. I meant dependency between components, not coupling.
It should still lower. Coupling in component based system is minimal. In OO approach, when you couple to interface X you also couple to everything X inherits.

Quote:If every component has its own std::string of its type, like "RenderComponent", there will be a whole lot of std::strcmp in your code, and comparing strings is not particularly efficient. (Alternatively, you could use integer values as IDs, but this would make your code incredibly hard to read.) But the real problem is when you do this several thousand times per step. Sending a message would mean querying an entity for a certain component. Doing this for every message sent will not be without a performance cost.
Which is why it's not done on per-message basis but around object creation time.

And again, does profiler agree? Various reports I'm aware of, seem to claim the exact opposite. Managing relations is rarely a bottle-neck even if no caching at all is used. And when it is, storing references solves the problem.

Quote:I literally mean the derefencing operator. (Foo.Component or Foo->Component) Component-entity modeling tends to use the dereferencing operator more than inheritance/polymorphism, and dereferencing does have a performance cost. It may seem trivial at first, but when you see the sheer number of times you will do it per frame, it will definitely be noticeable.


The only way to avoid memory references is by writing your entire application in CPU registers.

One of the benefits component-oriented design can bring is improved data locality.

Consider polymorphic scenario. Each object is composite and 1k-2k large. You allocate them from pre-allocated store (very tricky due to varying sizes). You end up either with per-type allocator or generic heap allocator:
[Player|H][Creature|H][Creature|H][Creature|H][Creature|H][Creature|H][Object][Object][Object][NPC|H][NPC|H][NPC|H][Crate][Crate][Crate]
You end up with N pools defined at compile time, where N is all different types of objects.

Health is 4 bytes, but these objects are located in different pools. So even though there is a container vector<ObjectWithHealth*>, iterating over that requires jumping over 1k (considering each object's size), causing almost mandatory cache miss.

Reorganizing pools here doesn't help, since each object is 1k in size, so even if all objects with health are moved into same pool, values are still 1k apart.

Compared to component-based design:
[Health|H][Health|H][Health|H][Health|H][Health|H][Health|H][AI][AI][AI][AI][Network][Network][Network][Network]
Here, all health components are allocated locally (4 bytes each). To update all of them, you simply iterate over, them benefiting from data locality.

The -> operator isn't representative of the cost. It can represent value that has been loaded into register (by compiler), it can point to top or bottom of stack, to two elements on heap located next to each other, a-> and b-> can point to same address, causing aliasing issues, or to value that is currently paged on disk.

Cost of -> will be from under 1 to hundreds of cycles.

Quote:Not much I can say to this. I guess we are trying to use the system for different purposes, if coupling is what you want for you objects and it is what I want to avoid.
Component based systems evolved because coupling in OO designs became unmanagable. I'm really not sure what you classify as coupling.
My main point in my last post was that while you could, for example, store all of the RenderComponents in a container so that you can iterate and update each one without RTTI, you could easily do the same thing with normal inheritance and polymorphism. You would keep objects in containers of IsRenderable instead of inheriting from an IsRenderable interface and querying every object at runtime for whether or not it inherits from the IsRenderable interface. By doing this, you can use normal objects that are more readable and have the advantage of instant access to, say, Renderable objects.

Again, does anyone know what proportion of games these days uses each object system?
Quote:Original post by Jetjayjay
If every component has its own std::string of its type, like "RenderComponent", there will be a whole lot of std::strcmp in your code, and comparing strings is not particularly efficient. (Alternatively, you could use integer values as IDs, but this would make your code incredibly hard to read.) But the real problem is when you do this several thousand times per step. Sending a message would mean querying an entity for a certain component. Doing this for every message sent will not be without a performance cost.

We use symbols which are strings that are evaluated as ID's. In code they look like this Symbol_("ai whatever component") but at runtime they are evaluated as a 64 bit integer id. It's both readable and effecient. The details of the implementation are not important but my point being you can have the best of both.

Quote:When the system is completely data driven, it doesn't really make a difference if you use the component-object system or inheritance/polymorphism.

I disagree. The idea of components are to model small atomic pieces of behavior. That small behavior can be glued together in a Lego like fashion. This gives designers the ability to combine components in ways that the original programmer never intended. If you want an actor that has a targeting ability with a spline following component you just give them a render component, target component, and spline component and you have a new type of actor. This actor will be light weight and not come with all the bloat that you would have if you put this into a giant class hierarchy.

An additional benefit, since the components are very atomic its easy to test all their features in isolation. Just give them a nice bindable public interface that other components can touch and you enable a very huge variety of options to a designer.

-= Dave
Graphics Programmer - Ready At Dawn Studios
Quote:Original post by David Neubelt
Just give them a nice bindable public interface that other components can touch and you enable a very huge variety of options to a designer.

Yes, I think the key is here: each component must have a small yet adequate interface. If one component need to 'reach into' other components in too many different ways, then perhaps your component divisions are in the wrong place. And if one component polls another so often that performance becomes a concern, perhaps you need to push the data when it changes via events/publish-subscribe/observer rather than via polling.

Quote:Original post by Jetjayjay
Again, does anyone know what proportion of games these days uses each object system?

I seriously doubt there are many games out there which don't do a little bit of both to some degree. Certainly this is not the sort of thing that is quantifiable.
Tangential and possibly dumb question:

I think I may simply be misunderstanding something, but the OP earlier made reference to the Strategy design pattern as being different from a component based object model.

I had been under the impression that the Strategy design pattern and a more component based object system were different ways to describe essentially the same thing - providing differentiation of object behavior via composition rather than inheritence. In other words, I had thought using a component model for game objects was an example of the strategy design pattern in action.

Does having a component object system mean something substantially different than this? (Or am I misunderstanding the design pattern...)
The strategy pattern does provide swappable behavior.

Making objects out of components does more than just behavior, and more than just swapping.
Why does everyone use so much indirection? Messages, IDs, Strings and the like? If a HumanRenderer component needs to render blood, it needs a reference to the HealthComponent, by its very definition:

class HumanRenderer {public:  HumanRenderer(const HealthComponent &health);private:  const HealthComponent &health};


Then, when it renders, it just refers to health. This removes the need for strings, IDs, etc. In fact, entities can simply store a flat list of components, with no way to retrieve a specific one. It seems counter-intuitive at first, but I think it can work. It has so far in my game anyway, though it does introduce some new ways of thinking.

One example would be the collision system. (I haven't actually coded this yet, but I believe it can work like this) When a CollisionSystem realizes that two PhysicalComponents are intersecting, it needs to notify the different components in the entity what has happened. But it has only a reference to the two PhysicalComponents. The PhysicalComponent will contain (passed in from the constructor) a string->Component mapping for "exposed" components, and a list of callbacks. The CollisionSystem calls each of the callbacks, passing in the map of the opposite physical component, leaving varius systems to inspect the other entity. The HealthComponent could check for a PowerupComponent in the opposite entity, the InventoryComponent could check for an ItemPickupComponent, etc.

Basically, my main point is that most of the indirection in this topic, and its code ugliness/performance hit seems to be unneeded. I think a functional component system could be built without messages or IDs/names (except for when truly needed, as in the collision example above). Components don't even need to have a reference to its parent Entity (Because the Entity doesn't need to know any kind of "name" for its components). They should merely contain references to the other components they need.

Am I completely missing other advantages to indirection and using messages and things? Or are there instances that i haven't yet thought of where my no messages and no way to actually access specific components given an entity will break down?

Other edges cases, like centering the camera on a specific entity, can simply become centering the camera on a specific PositionComponent, which nicely gives you the ability to do multiple viewports on a car/ship/whatever, for instance, since you can now have multiple components of the same type in the same entity.

This topic is closed to new replies.

Advertisement