Skip to main content
gamedev.net
🔒 Locked

Entity system: Avoiding casts when returning shared_ptr

Started by Corvwyn 6 replies 2.1k views
Original Post
Corvwyn
Corvwyn
I'm making an entity system for a game in C++, and I want a function getComponent that returns a component of a specified type.

I found an example that does this when fetching a component: entitySystem->getComponent(entity)
(This isn't the intended usage, I just don't want to drag too much code from the example)
Code: template<typename T> T *getComponent(Entity *e) {

return (T*)e->mComponents[T::familyId];
}


All my components are shared_ptrs, so I have to do this differently. How do I do the same thing with a shared_ptr? I want to avoid adding casts everywhere.
Corvwyn
Corvwyn

You could use dynamic_pointer_cast, perhaps?


Yeah, that could work. I'll check that out when I get back from work. Thanks.
crancran
crancran
I have seen this usage in the past and often questioned the benefits compared to using a messaging/event framework to queue events or API calls, allowing you to keep components of a separate families isolated from one another. For example, if the visual component needs position, the visual component or system sends an event "TransformQueryEvent" and the transform is wired to receive this event, populates the scale, position, and orientation and returns. The event call upon returning (which was nothing more than a callback) would then contain the data the visual needs.

At the heart of it all, it essentially performs precisely the same mechanic as you have outlined above, just yours has a more visual dependency using the entity as a mediator where-as other solutions using events use an event system as a mediator and have more of a vague relationship. I've heard it's easier to bind lua to an event based entity system rather than the above, however I've not stepped far enough into it yet to say I know whether that was true or not.
Corvwyn
Corvwyn
I got my code working. I did something like this:



template<typename T> std::shared_ptr<T> getComponentAs(EntityId id) {
auto iterPair = componentMap.equal_range(id);
for(auto iter = iterPair.first; iter != iterPair.second; ++iter) {
if(iter->second->getComponentType() == T::componentType)
{
return dynamic_pointer_cast<T>(iter->second);
}
}
return nullptr;
}



Regarding the choice of an entity system. I've read a lot of articles lately about the pros of using a system like this. I'm following the ideas described on this wiki: http://entity-systems.wikidot.com/.
Kylotan
Kylotan

I have seen this usage in the past and often questioned the benefits compared to using a messaging/event framework to queue events or API calls, allowing you to keep components of a separate families isolated from one another. For example, if the visual component needs position, the visual component or system sends an event "TransformQueryEvent" and the transform is wired to receive this event, populates the scale, position, and orientation and returns. The event call upon returning (which was nothing more than a callback) would then contain the data the visual needs.

The method shown here has the benefit that you write less code. A messaging approach means you have to write 2 functions to handle 1 piece of functionality that happens to require some data from elsewhere, one to request it, and one to handle it. That gets unmaintainable very quickly. Of course, having components directly access other components can get unmaintainable quickly too, if you have too much interaction between components, but at least then your connections are explicit.
crancran
crancran

[quote name='crancran' timestamp='1322574610' post='4888774']
I have seen this usage in the past and often questioned the benefits compared to using a messaging/event framework to queue events or API calls, allowing you to keep components of a separate families isolated from one another. For example, if the visual component needs position, the visual component or system sends an event "TransformQueryEvent" and the transform is wired to receive this event, populates the scale, position, and orientation and returns. The event call upon returning (which was nothing more than a callback) would then contain the data the visual needs.

The method shown here has the benefit that you write less code. A messaging approach means you have to write 2 functions to handle 1 piece of functionality that happens to require some data from elsewhere, one to request it, and one to handle it. That gets unmaintainable very quickly. Of course, having components directly access other components can get unmaintainable quickly too, if you have too much interaction between components, but at least then your connections are explicit.
[/quote]

I just don't understand why the explicit dependencies are often viewed as bad, particularly when you know the physics and render components are dependent upon position in order to perform their tasks. I had considered this approach when I actually had an entity class; however, since having allowed the "entity" to go the way of the dinosaurs, i opted for events instead.

But I did read an approach where you allow the systems to have explicit connections between one another rather than components since the logic is held in the systems and components are treated as data containers. So for example, when a physics component is first initialized (moved from inactive to active status), the physics system could get the transform data like below:


bool PhysicsSystem::startup(Engine* engine)
{
/* .... startup stuff here ... */

// lookup the entity transform system by interface and store a pointer
// to this interface during the startup process.
mTransformSystem = engine->getEntitySystem<TransformSystem>();

return true;
}

void PhysicsSystem::OnEntityActivate(const EntityActivationEvent& evt)
{
TEntityId entityId = evt.getEntityId();

// transform data
TransformData transform;

if(mTransformSystem->getEntityTransformData(entityId, &transform))
{
// store data in component
PhysicsComponent* component = getComponentByEntityId(entityId);
component->setInitialPosition(transform.getPosition());
component->setInitialOrientation(transform.getOrientation());
component->setScale(transform.getScale());
// activate component
activate(component);
}
}


The above equates to something similar to querying the entity for a specific component interface but instead I query component information via it's component system interface instead. This would allow me to change components and their behavior inside their respective systems and minimize the risk of exposure of those changes to outside components and systems because of using the interface. I realize there is no right or wrong way to approach this and it's a matter of preference a lot of times, but does one approach work better than others?
Corvwyn
Corvwyn
Interesting example. I'll have to check that out further.

This might be getting a bit off topic, but right now I'm using a system similar to what you describe. My components contain only data, and systems have the implementation. The systems have vectors of active components (some work left here).

Here's an example:


class RenderSystem : public ComponentSystem {
vector<shared_ptr<VisualComponent>> visualComponents;
vector<shared_ptr<PositionComponent>> positionComponents;
public:
void Process();
};

[font="monospace"][font="arial, verdana, tahoma, sans-serif"] [/font][/font]
[font="monospace"][font="arial, verdana, tahoma, sans-serif"]This is just from a test, and not a real implementation. I'm still working out the details. The way I do it now is that I manipulate the components, then the systems iterate over all its active components and does some logic.[/font][/font]
[font="monospace"][font="arial, verdana, tahoma, sans-serif"] [/font][/font]
[font="monospace"][font="arial, verdana, tahoma, sans-serif"]I'm doing some performance testing right now, and then I'll continue working on events etc.[/font][/font]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.