Quote:Original post by JetjayjayThat'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).
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?
Quote:That would probably solve many of the problems with all of the coupling between componentsThis 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-intensiveDoes your profiler agree?
Quote:and all of the dereferncing stuff is also fairly slowHow 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.