If you could show the basic concepts (Is there an entity class, or is there only an unique ID somewhere for example) and some headers, this would be awesome, cause I couldn't find such information on the net yet.
My basic concept is a clear seperation of system end entities, like the data-oriented design claims. So I built a generic
ComponentSystem, which holds a
std::vector<T> with the (homogenous component data).
If doing so, you are forced to deal with the fact, that resizing a vector will possibly move the current data out of its current spot to another location. That's why started to identify objects via an
ObjectID (just a typedef to an unsigned int). So I changed the component systems' API towards querying via ID.
This leads to the next problem: If everything is querying the objects, you'll need to speed that up. So I introduced another container, which I call "lookup vector". At this point I decided to preallocate the data and lookup vectors to a specific size (which is for
MAX_OBJECTS, just a const
ObjectID that is very large). So all systems preallocate memory while startup. So data and lookup vector are preallocated. I limited valid ObjectIDs to be >0 to use 0 as "nothing is set" (like nullptr, but without having pointers here).
Btw data is reserved, lookup is resized and filled with "0". The lookup's value is the index within the data vector. But "0" is also used as "not in list". So when adding an object, a new T is emplaced at the end of T and it's index is stored within lookup via
lookup[id] = index. So querying for an id will result an index (remember: index 0 is "object not found"). Around this basic concept, I built my entire ECS with the following systems:
- Physics (component with position, face direction, collision info etc.)
- Render (component with sf::Sprite etc.)
- Animation (component with thor::Animator etc.)
- Avatar (component with health, mana etc.)
- etc.
At my design each object is just an ID (not an instance of a specific struct/class). When creating an object, an ID is (externally, via a seperate ID management system) fetched and a physics and render component are acquired. If the object is animated, an animation component is also created etc.
Because there's no object itself, e.g. the (later) AI Component will store it's target's object id (instead a pointer or so). So if the target is removed, the AI system can recognize this when asking the physics whether there's such a component or not.
Well, about releasing objects: I prefer adding the ids to a "unused" vector, because objects might be released during udpate iteration. So I collect all "should-be-released"-ids and call each systems'
cleanup() method at the end of each frame.
I hope that helps
If not, just ask
/EDIT: The component systems header looks like this (not exaclty, I removed stuff for simplicity):
template <typename Id, typename T, std::size_t N>
class ComponentSystem {
private:
using container = std::vector<T>;
container data;
std::array<std::size_t, N+1> lookup;
protected:
std::vector<Id> unused;
public:
ComponentSystem();
virtual ~ComponentSystem();
/// Create and return an object with the given id
/**
* @pre The id mustn't be used, yet.
* @pre The id must be > 0.
* @pre The id must be < MAX_OBJECTS.
* @post The object's data is initialized according to T's default ctor.
* @param id to use
* @return reference to the object
*/
T& acquire(Id id);
/// Release the given object
/**
* The object is only marked for deletion. You need to call `cleanup()`
* after iteration to remove all marked objects.
* @pre The object must exist.
* @param id to identify the object with
*/
void release(Id id);
/// Query whether an object with the given id exists or not
/**
* @return true if the described object exists
*/
bool has(Id id) const;
/// Query const reference to an object
/**
* @pre The object must exist.
* @param id of the desired object
* @return const reference to the object
*/
T const & query(Id id) const;
/// Query non-const reference to an object
/**
* @pre The object must exist.
* @param id of the desired object
* @return non-const reference to the object
*/
T& query(Id id);
/// Cleanup unused object
/**
* Call this method to cleanup all unused objects.
*/
virtual void cleanup();
/// Query number of used components
std::size_t size() const;
/// Query maximum number of usable components
std::size_t capacity() const;
typename container::iterator begin();
typename container::iterator end();
typename container::const_iterator begin() const;
typename container::const_iterator end() const;
};
I derive e.g. from
ComponentSystem<ObjectID, PhysicsData, MAX_OBJECTS> at the physics system.