0
\$\begingroup\$

Every entity has some stats which change over time and some which are static for a given type of entity. For example current hp vs max hp: lets say humans have max 100hp, then every human would have an int hp member, but it doesn't make sense to have a const int max_hp=100 for all the humans since it's always the same thing.

That can be solved by using static member variables, but here's the catch: I want to load all the stats such as max_hp from a config file when the app starts, which precludes use of static.

The only way I know how to reconcile these two things is to use a singleton object, which provides entities their stats via something like singleton::instance().model("human").stat("max_hp"). However, every mention of singletons I have ever seen has been in the context of warning against their use. And I wouldn't want to use it anyway, because then my entity update methods lose referential transparency.

So in short:

  • I want to avoid duplicating data.
  • I want to load that data from file.
  • I want to avoid global state.
  • Update methods must be pure functions.

What strategy can I use to meet these requirements?


I require update methods to be pure because my engine is built around the concept of updating every entity in parallel, and the way to do that is to have entity.update be a pure function which takes an immutable reference to the world and returns a new entity [0]. So every entity starts its update by making a copy of itself, which is then mutated as it goes through the update process, until finally being returned to the world to become the manifestation of that entity in the new generation. Now that would be a lot of copying if only the dynamic data were copied! But to also copy all the (duplicate) static data every frame is just crazy, and I'm looking for the approach which would let me copy only the dynamic stats while still having access to the model data.

[0] https://www.youtube.com/watch?v=Uooh0Y9fC_M

\$\endgroup\$
2
  • 2
    \$\begingroup\$ Could you add the rationale behind your requirements? Sometimes, duplicated data is going to be faster than having to jump somewhere else in memory (cache miss), especially considering that if you decide to jump, you need a 32-bit pointer, which could as well be occupied by a 16-bit duplicated value instead. \$\endgroup\$
    – Vaillancourt
    Jul 28, 2016 at 15:55
  • \$\begingroup\$ That's a good point, I will add the rationale. Maybe some of my assumptions were mistaken! \$\endgroup\$ Jul 28, 2016 at 15:59

1 Answer 1

2
\$\begingroup\$

Put all those maximum, default or fixed values in a template or "archetype" object. Point each entity at its appropriate archetype during creation.

You can load archetype data from a file:

// human_archetype.txt
max_hp = 100
max_mp = 40
flammability = 2.5

Load that data from a file into a template object:

struct EntityArchetype {
  float getFloat(const std::string& name) const {
    return m_numberProperties[name];
  }

  const std::string& getString(const std::string& name) const {
    return m_stringProperties[name];
  }

private:
  std::map<std::string, float> m_numberProperties;
  std::map<std::string, std::string> m_stringProperties;
  // ...etc...
};

Load all archetype data files into some storage you can index by name or key so you can look them up when you create an actual entity. Store a reference to that archetype in the entity:

struct Entity {
private:
  Archetype* m_archetype;
};

Then you can provide access to it via some accessor (so a component can check it for default or maximum values, et cetera).

When creating an entity, you'd need to know the archetype so you can look up the Archetype pointer you want to use and pass it to the entity. You can often infer this lookup from from the creation of the entity itself (that is, if your entity creation is from a data file, the file can include a field that says which archetype to use).

This allows all entities that use the same set of defaults and constants to simply refer to one shared, immutable instance of those values. It's sort of like an overridable-prototype pattern.

For maximum generality, you could even make the archetype just another entity instead of its own special Archetype class, if you were so-inclined (and your entity interface supported the appropriate methods).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .