Quote:Original post by Numsgil
What special case? I think we're talking about different designs. I don't mean something where the core code has to be updated when you add a new object type. Just when you add a whole new subsystem. This is the sort of thing I'm thinking (pseudocode):
*** Source Snippet Removed ***
There's only one detail about that. You have the concrete type information available when you create the object. Then you discard that information, and use dynamic_cast to recover it - resulting in redundancy.
Quote:void NetworkLogic(Object *Ob)
{
INetworkable *networkable = dynamic_cast<INetworkable *>(Ob);
if(networkable)
networkable->SerializeToNetwork();
}
This is all fine and dandy, but it's direct consequence of discarding the type information which you already had, forcing you into performing a redundant type-cast.
Consider the case where only 3% of components are INetworkable. You will be required to blindly query every object for a random chance of it being networkable. The dynamic cast here isn't cause of performance problem.
The use case for network component will almost certainly look like this:
- When a client connects, it receives a set of all networkable objects
- When a new object is created, it's set to all clients
This can be extended to add range checks, but the complexity doesn't change.
And this led to my proposal. You *know* the exact type when you create the component, use that to your advantage.
struct INetworkable;typedef std::vector<INetworkable *> Networables;struct INetworkable{ INetworkable(Networkables & n) { n.push_back(this); // I believe this is legit } void SerializeToNetwork() = 0;};
This doesn't change including hierarchies in any way whatsoever, yet removes the burden of dynamic casts altogether. Whenever a client connects, you simply iterate through your Networkables instance and invoke the interface.
But usually, component-based designs are a replacement for rigid hierarchies.
Just to give a trivial example: IControler is entity controller interface. For AI it's one implementation, for Player it's another, for everything else, it's third.
What happens next is that NPCs and Player are almost identical, yet differ in controller only. Then you end up with:
- PlayerObject : public IPlayerControler
- NPCObject : public INpcController
You will need two identical, yet completely separate classes to do the same, because they differ in minor controller implementation.
Next solution will be to implement base class for those two which will have everything that Player and NPC share minus the controller. One particular problem solves, yet the issue persists. Until at some point, you will end up with a bunch of prototype abstract classes holding various common functionality minus the specialized functionality. Such as PlayerAndNPCAndStaticObject which will be base for those 3 object types.
And this is the reason why dynamic component systems which assemble components completely during run-time gained popularity. Those systems have no hierarchy, and have single base class containing a list of all components implemented. Each component has pointer to parent and its own type id in addition to whichever functionality it defines.
This not only completely removes coupling (components can be defined in DLLs and instantiated during run-time), but also doesn't lock you into complex chains of hierarchy resulting in absurd inheritance or illogical coupling.