• Advertisement
Sign in to follow this  

Is the Component Object Model Really Worth it?

This topic is 3300 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

The idea that an object is no more than the sum of its parts has gotten a lot of interest from programmers. But is this design system really worth implementing? There's a reason I posted this under Game Programming, and not under General Programming. In games, objects have a tendency to be less abstract than its other programming counterparts. Objects are solid things, for the most part, like "MainCharacter", as opposed to, say, "Client", in business software. This means that in a game, the components of an entity in the COM model are less likely to be as loosely coupled as they are theoretically supposed to be. For example, lets say that our object "MainCharacter" has a "Stats" component and a "Render" component. A monster has recently attacked our MainCharacter, so the latter's hit points have dropped. The StatsComponent has received the Damage message from the monster, and has dropped its Hit Points stat. Here's where the trouble begins. The RenderComponent is supposed to render MainCharacter with an amount of blood on him that according to the amount of damage done to him. Now what do we do? There are two ways to solve this problem: 1) Have the RenderComponent query the MainCharacter for a StatsComponent and get the hit points from it. This would introduce a tight coupling between our components that the component object model was supposed to avoid in the first place. Doing this defeats the purpose of COM. 2)Adding a hit points member to the RenderComponent that also gets affected by the Damage message sent by the monster's attack. The problem here would be the use of duplicate code. Every time you use duplicate code, someone throws a shoe at the President. If the flexibility of the behavior of a component object system is what you are after, you may want to take a look at the Strategy design pattern (http://en.wikipedia.org/wiki/Strategy_pattern) rather than build a whole object from scratch. So you can’t really use run-time flexibility as a reason to use COM. So, is the component object model really worth it? Feedback much appreciated.

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by Jetjayjay

2)Adding a hit points member to the RenderComponent that also gets affected by the Damage message sent by the monster's attack.
The problem here would be the use of duplicate code. Every time you use duplicate code, someone throws a shoe at the President.


Code? Or Data?

The solution to above is to let individual component subscribe to individual data in other components.

If renderer component needs health value, it subscribes to object's stats' hitpoints value.

And yes, data gets copied and replicated a lot, but since this type of systems are mostly concurrent these days, that is the preferred method anyway. You're stuck with buffered updates anyway (read/write modes).

See the Intel's Smoke demo, which achieves high degree of concurrency by using per-member updates.

Simplest way to achieve that is, if component X needs to be notified of Y::p property changing, it subscribes to that by providing local copy on 'p'. The system, when needed, copies value from Y::p to X::p. When, at some later point X needs updating, it has what system deems most recent value of ::p.

Share this post


Link to post
Share on other sites
Don't you think that adding a subscription system to the COM would make a messy system even messier?

Using the component-object model will probably end up, in the long run, being less efficient (there are a lot of dynamic_casts or other comparison functions that will be needed to implement COM) and less readable than a straightforward inheritance/polymorphism based class heirarchy. Introducing a subscription scheme to COM will cause very tight coupling between components and will require a lot of semantic programming that the system was meant to avoid in the first place. As I have already mentioned, the Strategy design pattern can achieve the same flexibility of behavior that COM can, without the need to build every object from scratch.

Share this post


Link to post
Share on other sites
A bit of a ramble, but here are my thoughts. There is a definite risk that going with some kind of composition instead of classic inheritance is full of pitfalls. However, like any other engineering practice, if you craft your components with foresight the benefits outweigh the costs. When we wrote our component-based entity system, we chose to do it because it allowed us to get away from the crippling rigid class hierarchies that we had developed in the past.

Our first crack at implementing characters and objects had everything broken down into such fine-grained details that the overhead of fetching data from other components and managing state was high, albeit completely generic and decoupled. We had query messages that could ask any sibling component for a piece of data by name. We could expose interfaces from multiple components so that an 'IStats' component-interface could be implemented by different components, be them MonsterStats, CharacterStats or PropStats, and queried by interface name by client components. It was crazy, hard to manage, and hard to get right.

Then we stopped, and evaluated whether this loose coupling was ... too loose, and compared it with our original goals. One of our goals was to keep a larger portion of our code base project to project without having to rip a lot of game-specific logic out of the base classes of our hierarchies. A COM system solved this. Another goals was to allow for component-based updates, where the logic, animation, rendering, etc. could be done in separate threads/SPUs, again solved by the component approach. Additionally, we wanted to add scripted components that could work alongside C++ components for rapid prototyping as well as data-only components, quick reload and state management. Our component entity system was solving those but we had been to zealous in our attempts to decentralize our old class-hierarchies.

Ultimately, when we rewrote our components we found that our components had more logic and data in them than originally expected, but still allowed a lot of mixing and matching, and more importantly, augmentation with secondary kind of components that were more about logic, eye candy or the like. We ended up with some pretty full-feature components: model, physics, animation. Each of these had a lot going on inside them.

In the case of something like 'Stats', it would end up being broken up among the needs of the game - A lot of entities in our game had health and could be damaged, so we had a Health component instead of a monolithic Stats component that did a couple of things. I received Damage messages, responding with a 'Dying' message if health < 0. It also had a MaxHealth variable and a HealthRegenerationPerSecond variable, used for resetting objects or healing. The Render component would look for the Health component, a definite dependency, but it has to be done somewhere, but at least anything in the game could have health, and know how to respond to it.

At some point, we realized, we had to stop worrying about the the system we were writing, working in a 'must be generic' mindset, and get back to writing the game, a 'must get this game working and fun' mindset. What the component system allowed was localized dependencies between components instead of complex dependencies both against hierarchies as well as within hierarchies. Ultimately, this yielded a game-entity system where the cost of entry for new object types was very low, formalized and flexible.

I know I really missed your points, but I think the proposed 'problem' is not so problematic. At some point in development, you need to stop building the system, and build the game. I think the Stats & Render problem is an example of it. At some point you just need to write in your Render component: if ( me.HasComponent( Health ) ) DrawBlood( me.GetComponent( Health ).GetHealth() );

Best of luck.

Share this post


Link to post
Share on other sites
Quote:
Using the component-object model will probably end up, in the long run, being less efficient


It worked for Dungeon Siege back in 2002. That document also makes for good read on practical issues encountered, as well as solutions to them.

Many others have used such systems with success.

But yes, developing fully run-time customizable, data-driven system will have higher resource requirements than hard-coding everything.

The difference is, with inheritance, all changes need to be made directly in code. With component based system, everything can (but doesn't need to) be done by content developers who will never touch actual code.


And I'm not really sure how COM is involved in all of that. It may be foundation of Windows, but it's not really such a great technology, and is primarily intended for IPC, not light-weight data representation.

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
And I'm not really sure how COM is involved in all of that. It may be foundation of Windows, but it's not really such a great technology, and is primarily intended for IPC, not light-weight data representation.


Sorry for the mix-up with the Windows COM. What I was referring to was the concept of the Component Object Model as a system, not the Windows foudation stuff.

Antheus, so you mean that data in the object that is meant to be more frequently accessed by the rest of the objects would be kept in more goal-specific and focused components, and larger components, like the RenderComponent, would be larger and less accessible to the rest of the entities' components?

[Edited by - Jetjayjay on December 27, 2008 2:11:49 PM]

Share this post


Link to post
Share on other sites
Yeah I really don't recommend referring to your compoent architecture as "COM", since you're likely to inspire nightmares and flashbacks in those of us that have had the "pleasure" of doing extensive work with COM. :P

Share this post


Link to post
Share on other sites
Quote:
Original post by Jetjayjay

Antheus, so you mean that data in the object that is meant to be more frequently accessed by the rest of the objects would be kept in more goal-specific and focused components, and larger components, like the RenderComponent, would be larger and less accessible to the rest of the entities' components?


You have multiple types of dependencies. For example, Render system needs to work after Physics system. In trivial design this means:
while (true} {
for (IPhysics p : physics) p.update(dt);
for (IRenderable r : renderables) r.render();
};

This way you hard-code the dependencies on system level. But this only gets you so far, since relations are implied.

In your case, Human has both, Health (not like Rock or Mesh or Sound) and custom renderer. And relation between Health and HumanRenderer exists between those two on per-object basis.

When you create your human, you specify all that:
Entity * entity;
entity.add(new Health(0,100));
entity.add(new HumanRenderer());
Of course, human renderer just adds blood, so HumanRenderer<-BasicRenderer<-IRenderer.

Renderer is opaque, but HumanRenderer needs to know about health:
HumanRenderer {
HumanRenderer() {
// dynamic cast here
this->health = this->getOwher()->getComponent<Health>();
if (this->health == NULL) this.health = Health::getDefaultInstance();
};
virtual void render() {
....
// read an int non-polymorphically
render_blood(this->health->current_value();
}
Health * health;
// Health can be just int or std::pair<int, int> or ....
}


One dynamic cast per creation, and non-polymorphic access. Coupling between HumanRenderer and Health *interface* is strong, but their implementation coupling is trivial and minimal. Interface coupling needs to be strong, since they are very closely related.

But, human renderer is now capable of rendering anything and everything with Health by overlaying blood.

Of course, things get more complicated. What happens if Health goes away (many systems don't allow components to be removed), is there really need to redraw health? What if it's cached on texture and doesn't need redrawing, how exactly is getComponent<> implemented? And so on....


Eventually, you end up with fine-grained, meta-information rich object model which declares all of the above and more.


For concurrency, copying values if often desirable. For example - renderer and logic run in separate threads. Health of all objects will change rarely, typically it will be just 2 or 3 entities which will be affected.

Logic does its stuff, and 3 entities change health. They post HealthChanged(old,new) message.
At some later point, renderer is activated. Before running, it checks for all messages sent to it and finds 3 HealthChanged messages. It sends delivers these messages to its own IRenderer components which update their local copies. Finally, it redraws the screen.

So instead of locking or copying entire state, small, per-component portions of data are sent across threads which continue running regardless of each other.


But a lot depends on implementation itself, as well as design. One size doesn't fit all, and it's hard to predict where bottle-necks will lie. But considering the original dillema - polymorphism is not the only way to realize such a system. Much of functionality can be defined as interaction between two concrete non-polymorphic interfaces.

Share this post


Link to post
Share on other sites
I don't think that it's worth it yet. Trying to implement a class as a series of components nicely is just too much of a pain in current languages.

I think that it might become viable when languages allow multiple inheritance a little less shoddily, or more easily allow properties to be passed about.

Share this post


Link to post
Share on other sites
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 would probably solve many of the problems with all of the coupling between components, but I'm still not convinced that component-entity programming is worth the trouble you get in return. RTTI is pretty performance-intensive, and all of the dereferncing stuff is also fairly slow (not to mention almost completely unreadable). 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. In games, as I have mentioned above, objects are not instances as abstract as they are in other software, so you will be getting a lot of this kind of code:

int myFoo = this->parentEntity->GetComponent(FooComponent)->foo;
if (!myFoo)
{
myFoo = 5;
}

The thing is, that isn't even exaggerating the way it would work. You would see that all over your code. Not pretty, and has plenty of performance overhead.

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. 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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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?





Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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...)

Share this post


Link to post
Share on other sites
The strategy pattern does provide swappable behavior.

Making objects out of components does more than just behavior, and more than just swapping.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Quote:
Original post by matthewbot
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?


Because then you lose the ability to dynamically generate entitys from data files. For example, if you have a data-file like this:


entity name="Soldier"
component type="position"
component type="health" initialValue="100"
component type="HumanRenderer"
... etc


Creating the components is really difficult because the system would need to know the graph of all dependencies up-front and ensure it creates them in the right order. It also means if you add a new component and you want your HumandRenderer to take a dependency on it, for example, you need to update all of your definitions to include the new component.

By doing the dependencies dynamically, you avoid all those problems. You add all the components to the entities at once (using the default constructor for simplicity) and then they can just query each other for their properties, etc. If you add a new kind of component to the system, you don't have to update all the entity definition files - just make sure components which want to use it can handle the case where it's no there.

Share this post


Link to post
Share on other sites
My system creates entities from files just fine, though I guess it might be complex. In the text file, component descriptions have both a unique name, and a component type name, and links between them are specified by a property containing the other component's name. You have to put them in the correct order by hand, but its not much harder than declaring a variable before its used anywhere. The reading code parses the components one by one, creating them and saving them to a map by name as it goes. When creating a new component (done by a factory), a map of all previously constructed components is passed in. The factory looks up the component names to find the actual components, and passes them to the constructor.

So the net result is that you need a factory for each kind of component, to take the properties and previously constructed components, and look up component names for the components constructor. But would you not still need (albeit simpler) factories for the dynamic dependency approach, to handle the properties? Or at least a factory/callback to match the component name to the invocation of new?

Share this post


Link to post
Share on other sites
Quote:
Original post by matthewbot
So the net result is that you need a factory for each kind of component, to take the properties and previously constructed components, and look up component names for the components constructor. But would you not still need (albeit simpler) factories for the dynamic dependency approach, to handle the properties? Or at least a factory/callback to match the component name to the invocation of new?


In the component-based system I've been using, I use files similar to the ones I described in my last post. Then there's a couple of methods that the components use to query each other. Here's an example:


class Entity
{
private:
std::map<int, Component*> components;

public:
template<typename T>
public T *GetComponent()
{
std::map<int, Component>::iterator it = components.find(T::identifier);
if (it == components.end()) return NULL;
return dynamic_cast<T>(*it);
}
};

class Component
{
protected:
Entity *entity; // the entity we're contained in
// ...
};

class HumanRenderer : public Component
{
private:
Health *health;

public:
void Init()
{
health = entity->GetComponent<Health>();
}

static const int identifier = 1234; // unique per component
};




So you're still only fetching an instance of the "Health" component when the object is constructed (actually, when Init is called which is after everything is constructed). But the flexibility comes from the fact that if there's no Health component, you simply get NULL and can handle it as required. To add new components, you don't need to modify the factory or the data files (execpt for those that you want to include the new component in). You just modify the places where it's actually needed.

I can see that your system would also work, but I just think it would be harder to maintain since you've got multiple places to modify when a new component is introduced or when a new dependency is added.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement