Yep, honestly for a smallish-type game, I think your more simple ECS approach is probably best, Elias. Like I said earlier, it gives all the flexibility of an ECS, a clean simple solution, but just not the performance benefits (which you likely don't need).
Where do you get the "bitwise copy" notion from?
The default copy ctor does not do a bitwise copy - it does a member by member copy.
And if you are memcpy()'ing (a bitwise copy) an object, then it better be std::is_pod.
What exactly do you mean?
Oh, yeah sorry. That was confusing on my part.
What I mean is that normally the constructor, copy constructor, assignment operator and destructor is where you would (de)allocate new memory, if needed. For example, let's say I have an ActiveEffects component, which stores what effects (e.g. spells in an RPG) an entity is currently affected by.
// This is a component which stores effects on an entity
class ActiveEffects
{
private:
// These are the individual effects which this entity has.
// e.g. Maybe I cast a spell called "wind runner" on myself that makes me run fast.
// I need Effect* pointers because there are different kinds of Effect subclasses,
// e.g. MoveSpeedEffect, AttributeBoostEffect, etc.
std::vector<Effect*> m_effects;
};
Normally, you'd see those pointers and think, I need to write a whole lot of constructor/copy constructor/assignment operator/destructor logic, or I might end up with memory leaks or duplicate pointers.
For example, your copy constructor might look like this:
ActiveEffects::ActiveEffects(const ActiveEffects& i_rhs)
{
std::vector<Effect*>::const_iterator it = i_rhs.m_effects.begin();
std::vector<Effect*>::const_iterator end = i_rhs.m_effects.end();
for(; it != end; ++it)
{
const Effect* pOriginal = *it;
// This next line doesn't even actually work, because Effect* is pointing at
// a subclass, but you get the idea...
Effect* pCopy = new Effect(*pOriginal);
m_effects.push_back(pCopy);
}
}
So, now every time your vector of ActiveEffect components moves in memory, the copy constructor and destructor is called on every each one. This is a whole bunch of newing in the copy constructor, and a whole lot of deleting in the destructor. That's really stupid, because we aren't changing any effects.
Move constructors can solve this. But now every component in the game, and
every data member they contain needs to have:
- assignment operator
- move assignment operator
- copy constructor
- move constructor
That's fine, and that's what they're designed for.But my approach is to skip all of those.
The default copy constructor does exactly what I want - it copies the pointers without newing or deleting the Effects. But that means I can't delete any memory in the destructor (if I did, I would delete all my Effects every time the ActiveEffects vector grew). Instead, I put my destructor logic in a function called Component::Destroy(), which is called by the ComponentSystem whenever we actually want to destroy a component, rather than just moving it.
Haha, wow this thread is going crazy. 2 new posts while I was typing this up! Interesting game and tech discussion, I guess.