This question and its answers move against utilizing an entity component system where the componentcomponent itself is just a generic classgeneric class with a dynamic container for the attributes ofan dynamic container for the attributes/properties of the component.
Some definitions for this thread:
- Entity = ID + collection of components
- Component = Container (
std::map<attribute_name, attribute_value>) of attributes/properties, such as health, age, name, gender, etc.
I understand and am aware of the reasons against it (nodrawbacks, such as no compile-time type safety, and added overhead of looking up values from the container, et cetera). Nonetheless, I currently am working on/with such a system, with the following argument, which I would now like to put to the test: dataData-driven design.
If all the attributes of the component are in a dynamic container, it is very easy (in fact,more correctly: possible at all) to add new ones later on, outside of the source code. So you can easily script new attributes later on. And dumping/saving the state of the component is a piece of cake.
HoweverExample:
My component base class has a map for all the attributes/property:
typedef std::map<std::string, PropertyBase*> PropertyMap;
So an actual component looks something like the following. In the constructor I populate the attributes I want the component to have, thatby adding them to the map. Later I can read and update them if needed, but always with a performance penalty, since I first have to find them in the std::map.
class Person : public ClonableComponent<Person>
{
public:
Person(ComponentFactory* const pFactory, ComponentType type, ComponentID id, GameEntity* const pEntity)
: ClonableComponent(pFactory, type, id, pEntity)
{
// General
addProperty(new Property<int>("age", PROP_INT, 0));
addProperty(new Property<String>("gender", PROP_STRING, "unknown"));
addProperty(new Property<String>("currentLocation", PROP_STRING, "unknown"));
// Feelings
addProperty(new Property<bool>("isTired", PROP_BOOL, false));
addProperty(new Property<bool>("isHungry", PROP_BOOL, false));
addProperty(new Property<bool>("isThursty", PROP_BOOL, false));
}
void enterLocation(GameEntity* pLocGE)
{
if(pLocGE->hasComponent(COMP_TYPE_LOCATION))
{
updateProperty("currentLocation", pLocGE->getName());
updateProperty("casinoEnteredDateTime", (int)std::time(0));
}
}
void leaveLocation()
{
updateProperty("currentLocation", std::string("unknown"));
}
};
Bottom-line: That thread made me think, and I am now doubtful that my approach will work well in the long run (maintenance, scaling and performance wise) and I am therefore considering moving to an directly typed approach with no look-up overhead. At, but at the same time I don't want to loose that data-driven extendability.
I thought of a hybrid approach: keepSo a hybrid approach could be: Keep all known variables as actual language variables but also add a container (std::map) for scripted/data-driven variables. Therefore I'd know that my internal access is very fast and only the later added scripted extension would have the slow look-up and the other mentioned penalties.
WhatQuestion:
What do you use / would propose as an approach for a data-driven ECS in terms of attribute storage/handling? How do you keep it extendable? Would you recommend such a hybrid approach (real variables internally, container for new, external definitions)?