0

I'm implementing the factory pattern for component creation and want to implement a singleton container for all instances of each type created by the factory. Ideally this would be one vector for each type created in the factory.

This would be really easy if I could keep base class pointers in the vector, but alas my use case would vastly prefer all the instances be stored contiguously instead of wherever new puts them to get as many cache hits as possible.

I was thinking about doing something like this for the factory map:

Map<string,pair<constructorFnPtr, vector<baseClass>>

This has the issue of losing the data from the derived class as it is cast to the base class.

I was also thinking that a pointer to a vector as the second member of the pair would be a good way to do it, but I'm not sure how that could be implemented while still having a different data type stored in each vector. I don't think this would be possible since the templated vectors are all technically different classes.

Is there any way to do what I'm trying to do? I've been trying to figure something out for the past couple days with no luck.

Alternatively if there is another good way to store the vectors (ie as a static member of the component class) I'm open to any suggestions like that as well!

  • try using `dynamic_cast` where you get the type you want , but if the instance is not that type it will return null or throw: `dynamic_cast(myour_map["instance"].second[0])` if "instance" is not `TypeIWant` it will return null or throw (based on compiler settings) – Raxvan Oct 01 '14 at 08:20
  • Yes that works for getting the instance out of the factory, but it doesn't work for putting it into a vector of instances. I don't want a vector of pointers because the overhead for fetching them from main ram for every instance every time they're iterated through is very high. – Allan Deutsch Oct 01 '14 at 09:15

2 Answers2

2

I wouldn't consider using the factory pattern for components but rather the object pool pattern since you will likely want to use the factory pattern for managing the creation of entities instead.

I use a base Component class for all components. This allows me to be able to manage some static run-time type information for each component and expose a set of common methods. I then implemented an interface IComponentPool that is the contract for my component object pool. Then I defined a templated class ComponentPool<T> that is derived from IComponentPool. Inside this template class is where I manage two vectors/arrays. Then there is a ComponentPoolMap that exposes some map-like behavior to lookup a ComponentPool<T> based on component types.

Now in the ComponentPool<T> class, the first array is a sparse array used as a lookup. It holds an index offset to where the component resides inside the second dense packed array. The sparse array could simply be a means to take an EntityId and convert it to where component resides. A more sophisticated handle system can be easily implemented top of this type of framework if you want.

The idea here is that the packed dense acts as your contiguous memory buffer that you can easily iterate in a cache friendly manor in tight loops but the sparse array provides a single-level of indirection lookup on a per entity basis for components.

Now the factory pattern is where you create your Entity or EntityId handle to an entity and create all the necessary aspects that make up an Orc, Zombie, or whatever for your game. The factory acts as a layer that sits on top of your game object system of which your ComponentPoolMap is likely only a small slice of that system.

Naros
  • 19,928
  • 3
  • 41
  • 71
  • 1
    Nice answer. I also has a question. Another post (https://gamedev.stackexchange.com/a/59328) states that creating function like `EntityFactory::CreatePlayer/CreateBullet` *defeats half the purpose of component-based design*, but your solution states that creating factory-function for `Orc/Zombie` aspects is OK. While I agree with you, I would like to hear more of your valuable opinion toward that post. Thank. – cppBeginner Jan 24 '18 at 02:06
  • 2
    The author there is simply pointing out that if you can turn the factory method for creating an Orc or Zombie into a data-driven method that can take a definition for how to construct an Orc/Zombie and accomplish the same task, you gain the ability to do rapid prototyping which is often useful during development. A factory method is a great starting point, one which you can easily refactor into a data-driven archetype solution should you find you need or desire such flexibility. – Naros Jan 24 '18 at 14:58
0

You can't do this with a std::vector of contiguous objects.

The reason is: a factory is supposed to build objects, and then return a pointer to them. The problem is that the pointer is going to be stored around in your application, while the next call to your_vector.push_back() is likely going to invalidate them, as the C++ documentation says:

If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated. Otherwise only the past-the-end iterator is invalidated.

So, your next call to the factory can invalidate all your previous calls.

nyarlathotep108
  • 5,275
  • 2
  • 26
  • 64
  • I actually did say that for my use case a pointer to the element in the vector isn't optimal anyway. The index is stored instead. – Allan Deutsch Oct 09 '14 at 01:42
  • Than you could create a custom smart-pointer-like class, returned by the factory, which stores the index instead and then with the dereference operator calls internally the factory to retrieve the actual instance. This operation is obviously slower than using a direct pointer to access the instance, but you'd have the contiguous memory storage as well as less heap allocation call. It could be faster, or it could be slower, depending on the usage. I think for a general usage it is just slower. – nyarlathotep108 Oct 09 '14 at 07:50
  • A direct pointer would be faster until the vector needs to grow and every pointer to any of it's elements becomes invalidated. Thanks for the custom pointer idea though, I didn't think of that and it would work really well for me! Wish I'd come up with that a week ago haha – Allan Deutsch Oct 09 '14 at 11:39
  • Eheh I know. If you could test the performance of this solution, it would be great to know them! – nyarlathotep108 Oct 09 '14 at 13:53