I'm working on an Entity Component System library. Right know I'm trying to clean up the Entity/Component part (only the code which manage the data).
But I have a class which I think is not really readable. Maybe the implementation can be improved too, but I reduced the implementation to be small and fast, so it's not really why I'm here (but every review is always welcome).
DefaultManager.hpp:
#ifndef DEFAULT_MANAGER_HPP
#define DEFAULT_MANAGER_HPP
#include "Defines.hpp"
#include "HandleEntry.hpp"
#include <cstddef>
#include <vector>
#include <type_traits>
//!
//! @namespace ECS
//!
namespace ECS {
//!
//! @class DefaultManager
//!
template < typename Object,
bool Shared >
class DefaultManager {
//-----------------------------------------
//! Allow iterator to access the Pool
template < ECS_INT Filter >
friend class iterator;
//-----------------------------------------
//! GoogleTest Define
#ifdef UTESTS
#include "DefaultManager.utests"
#endif
//-----------------------------------------
private:
static const size_t BASE_SIZE = 1024;
//-----------------------------------------
//! Internal Structs
private:
struct ObjectPair {
std::conditional_t<Shared,
HandleEntryShared,
HandleEntry> entry;
Object data;
};
//-----------------------------------------
//! Attributs
protected:
std::vector<ObjectPair> m_pool;
ECS_INT m_activeEntryCount;
ECS_INT m_firstFreeEntry;
//-----------------------------------------
//! Iterator
public:
//! TODO: Need specialization for Filter = 0 (just remove the check on the filter)
template < ECS_INT Filter = 0 >
class iterator {
private:
size_t m_index;
DefaultManager<Object, Shared>& m_manager;
public:
using difference_type = ptrdiff_t;
using size_type = size_t;
using value_type = Object;
using pointer = Object*;
using const_pointer = const Object*;
using reference = Object&;
using const_reference = const Object&;
using iterator_category = std::bidirectional_iterator_tag;
iterator(DefaultManager<Object, Shared>& manager,
size_t index = 0)
: m_index(index),
m_manager(manager)
{}
iterator& operator++() { while(!isValid()) ++m_index; return *this;}
iterator& operator++(int) { iterator it = *this; ++(*this); return it;}
iterator& operator--() { while(!isValid()) --m_index; return *this;}
iterator& operator--(int) { iterator it = *this; --(*this); return it;}
bool operator==(iterator other) const {return &m_pool[m_index] == &other.m_pool[m_index];}
bool operator!=(iterator other) const {return !(*this == other);}
reference operator*() { return m_manager.m_pool[m_index].data; }
bool isValid() const {
Object& obj = m_manager.m_pool[m_index].data;
return (obj.type() & Filter) == obj.type();
}
};
template < ECS_INT Filter = 0 >
iterator<Filter> begin();
template < ECS_INT Filter = 0 >
const iterator<Filter> cbegin() { return begin<Filter>(); }
template < ECS_INT Filter = 0 >
iterator<Filter> end();
template < ECS_INT Filter = 0 >
const iterator<Filter> cend() { return end<Filter>(); }
//-----------------------------------------
//-----------------------------------------
//! Constructors
public:
DefaultManager(size_t allocSize = BASE_SIZE)
: m_pool(allocSize),
m_activeEntryCount(0),
m_firstFreeEntry(0) {
}
//-----------------------------------------
//! Getters and Setters
public:
Object& getObject(const ECS_INT index) {
return m_pool[index].data;
}
//-----------------------------------------
//! Functions
protected:
//!
//! @brief Used by the manager to extend the pool when it's needed
//!
//! Add + BASIC_SIZE to the current pool capacity
//! Copy current data into the new pool and init the new HandleEntry
//!
void extendPool();
//! @brief Primitive function used by the manager to release the data from one pool entry
//!
//! Put the activate bit to false for the pool entry whose index is specified as a parameter
//! Update the manager firstFreeEntry, depend on the data released position
//!
//! @param index the index where the data to release is positionned.
void releaseObject(const ECS_INT index);
public:
//! @brief Specifie to the manager the need to reserve some memory space
//!
//! Use std::vector.resize() to reserve memory for the pool
//! Initialize HandleEntry for every ObjectPair reserved
//!
//! @warning if you attempt do to so for every DefaultManager, think about change BASIC_SIZE
//! @param allocSize the size to reserve in bytes
void reserveObjectMemory(size_t allocSize);
//!
//! @warning Don't use, not sure to be keep
//!
void reset(size_t allocSize = BASE_SIZE);
//!
//! @brief Ask the manager to give a data segment from the pool
//!
//! Check if the pool has enough memory left (extend if not)
//! Take the first free entry of the pool and activate the entry
//! Initialize the data with args
//!
//! @param args pack of arguments you need to initialize your Object
//! @return the index of the activated object
template < typename ... Args >
ECS_INT addNewObject(Args && ... args);
//!
//! @brief Ask the manager to assign the object given to another entity
//!
//! Just increment the assigned counter of the HandleEntry which correspond
//!
//! @warning this function need the Shared from DefaultManager<typename Object, bool Shared> to be true
//! @param index the index of the already added Object
void assignExistingObject(const ECS_INT index);
//!
//! @biref Remove need to be called when an entity revoke her access to an Object
//!
//! Depend on Shared value
//! if Shared == true --> Will decrement the assigned counter and if the counter reach zero, release the memory
//! if Shared == false --> Will just release the memory of the Object selected
//!
//! @param index the index of the data that you want remove
void removeObject(const ECS_INT index);
};
}
#include "DefaultManager.tpp"
#endif
DefaultManager.tpp:
namespace ECS {
#define TEMPLATE_ARGS typename Object, \
bool Shared
#define CLASS DefaultManager<Object, Shared>
#define ITERATOR CLASS::iterator<Filter>
//! Iterator
//!------------------------------------------
//!
//! Begin
//!
template < TEMPLATE_ARGS >
template < ECS_INT Filter >
ITERATOR CLASS::begin() {
ITERATOR it(*this);
while (! it.is_Valid())
++it;
return std::move(it);
}
//!
//! End
//!
template < TEMPLATE_ARGS >
template < ECS_INT Filter >
ITERATOR CLASS::end() {
ITERATOR it(*this);
while (! it.is_Valid())
++it;
return std::move(it);
}
//!------------------------------------------
//!------------------------------------------
//!
//! ExtendPool
//!
template < typename Object,
bool Shared >
void DefaultManager<Object, Shared>::extendPool() {
size_t i = m_pool.capacity();
m_pool.reserve(m_pool.capacity() + BASE_SIZE);
for (;i < m_pool.capacity(); ++i)
m_pool[i].entry.nextFreeIndex = i;
}
//!
//! ReleaseObject
//!
template < typename Object,
bool Shared >
void DefaultManager<Object, Shared>::releaseObject(const ECS_INT index) {
if (index < m_firstFreeEntry) {
m_pool[index].entry.nextFreeIndex = m_firstFreeEntry;
m_pool[index].entry.active = false;
m_firstFreeEntry = index;
}
else {
m_pool[index].entry.nextFreeIndex = m_pool[m_firstFreeEntry].entry.nextFreeIndex;
m_pool[m_firstFreeEntry].entry.nextFreeIndex = index;
m_pool[index].entry.active = false;
}
--m_activeEntryCount;
}
//!
//! ReserveObjectMemory
//!
template < typename Object,
bool Shared >
void DefaultManager<Object, Shared>::reserveObjectMemory(size_t allocSize) {
m_pool.reserve(allocSize);
for (unsigned int i = 0; i < m_pool.capacity(); ++i)
m_pool[i].entry.nextFreeIndex = i + 1;
}
//!
//! Reset
//!
template < typename Object,
bool Shared >
void DefaultManager<Object, Shared>::reset(size_t allocSize) {
m_pool.resize(allocSize);
m_pool.shrink_to_fit();
for (unsigned int i = 0; i < m_pool.capacity(); ++i)
m_pool[i].entry.nextFreeIndex = i + 1;
m_activeEntryCount = 0;
m_firstFreeEntry = 0;
}
//!
//! AddObject
//!
template < typename Object,
bool Shared >
template < typename ... Args >
ECS_INT DefaultManager<Object, Shared>::addNewObject(Args && ... args) {
if (m_activeEntryCount >= m_pool.capacity())
extendPool();
const ECS_INT index = m_firstFreeEntry;
HandleEntry& nHandle = m_pool[index].entry;
m_firstFreeEntry = nHandle.nextFreeIndex;
nHandle.nextFreeIndex = 0;
nHandle.active = true;
m_pool[index].data = Object(std::forward<Args>(args) ...);
++m_activeEntryCount;
return index;
}
//!
//! AssignExistingObject
//!
template < typename Object,
bool Shared >
void DefaultManager<Object, Shared>::assignExistingObject(const ECS_INT index) {
// Waiting for if constexpr
}
//!
//! RemoveObject
//!
template < typename Object,
bool Shared >
void DefaultManager<Object, Shared>::removeObject(const ECS_INT index) {
//Waiting for if constexpr
releaseObject(index);
}
}
The most weird code seems to be the iterator and begin/end function. The whole code is here if you want to see more.