Quote:Original post by Hodgman
Speaking of dependance issues - these can be a real pain too. It might be worthwhile giving all components an EntityIsFullyConstructedNow() virtual function, so any initialization requiring other components can occur after all components have been created. These dependencies could be used as a sorting-order when calling this function, and no cycles should be able to exist (in debug you could check for cycles and assert/log the error).
Our system has three stages for components: 1. constructor, where the defaults are set without any knowledge of your peers/siblings - here components register themselves with subsystems; 2. Component::OnCreate - by this time any per-instance changes to a component instance have been set from source data files as defined in our level editor. 3. Component::OnPostCreate - by this time all of the components in an entity have been initialised, at least internally to themselves, and are now considered valid to their siblings. This solves all of our problems, and we are pretty satisfied with how it works.
Quote:Could you provide an example where an example where an entity might need multiple components of the same type? I'm curious to see situations where that problem arises. Does the usage of a multimap instead of a regular map (as I mentioned in my first post) sound like a reasonable solution?
We have multiple instances of some components where we want to expose multiple prop states in our UI. It just works/makes sense having support for more than one instance. Our components are stored in a table of offsets that is shared among each instance of the same 'pattern' of components. We don't allow at-runtime composition. Instead it is defined and performed in the pipeline, so much like a single virtual function table is shared among all instances of a class, a single instance of the patterns' component table is shared among all instances of a pattern. This lets us put a lot of rich data into a shared structure, minimizing memory usage while maximizing verbosity.
Quote:I have two ways of registering interfaces - static (compile time) registration, which uses macros to create a marshalling function - and dynamic registration, which uses a std::map within the entity.
The marshalling technique was suggested to my by Antheus in this thread, I can post more details about it when I'm at home. Basically it's a linear search through each component, but there is no storage overhead in the component or entity. As you suggest, components usually store the returned pointers in member variables to avoid searching every frame.
Sounds familiar to what we do - in fact, today I went and implemented GameEntity::FindInterface<T>() method, and used simple macros to define static hash IDs for our interfaces:
class ITransform{ DECLINTERFACE( ITransform ) virtual Matrix4& GetTransform() = 0;};
where DECLINTERFACE is
#define DECLINTERFACE(INTERFACE) static u32 GetInterfaceID() { static u32 hash = HashString( #INTERFACE ); return hash; }
Allowing us to do
template<typename T>T* GameEntity::FindInterface(){ FindInterfaceByID( T::GetInterfaceID( ) );}
I've expanded this to actually return an InterfaceIterator<T> so we can iterate over multiple interface implementations. The game teams are really excited about the abstraction provided.
XezekielX - as far as testing the pointer against null - that's one of the things you have to get used to. Instead of testing it against a NULL, consider it more like "If (HasAnimation) { do something }" instead of "if (pAnimation != NULL ) { do something }".
Also, components should not have a virtual Update() method. You do not want to do foreach( Entity ) foreach ( Component in entity ) - it's too much work thats not needed, since more components will have an empty Update() method. Instead you want to do this:
AnimationComponent::AnimationComponent(){ Animator::RegisterComponent(this);}
and in the Animator, invoked from the right place in your loop:
void Animator::UpdateAnimations( float deltaT ){ foreach( RegisteredComponent component ) { component->UpdateAnimation( deltaT ); }}
This way you only update animation components, not all components. You can also make your animation component remove itself from the list if the animation is inactive, say in AnimationComponent::Stop(), and add itself back in through AnimationComponent::Play() - this way you only touch, per frame, the objects that need to be updated. Invoking a host of empty Update() methods is a waste of cycles, and with many entities, each of which as multiple components, the empty virtual call starts to add up - at least in my console world.
Good conversation, interesting stuff - keep it up.
.S
[Edited by - Sphet on June 10, 2009 9:27:25 AM]