3
\$\begingroup\$

I implemented an Entity and ComponentManager< T > for my new Entity-Component-System (ECS) architecture in my game engine. My older ECS maintained the Entity to Components mapping on the Entities themselves, and the Component to Entity mappings on the Components themselves; all using custom pointer types, since the actual Entities and Components were stored elsewhere in contiguous memory. This does, however, not result in minimally and optimally packed and aligned Entities and Components, as is the case for (or at least could be achieved with) my new ECS architecture.

Each Component type is managed by a separate container, ComponentManager< T >, that encapsulates three separate containers for the following data:

  • A contiguous block of memory holding the Components. In most cases, Components will be processed in front-to-back order without requiring the corresponding Entities per frame.
  • A contiguous block of memory holding the Entities. The Component at index i in Components is associated with the Entity at index i in Entities (i.e. a map where the keys and values are stored separately as opposed to key-value pairs). In rare cases, Component-Entity pairs are processed in front-to-back order (at most once per ComponentManager< T >) per frame.
  • An unordered mapping from Entities to Component indices in Components. In rare cases, the Component associated with a given Entity needs to be retrieved (at most once per ComponentManager< T >) per frame.

Since Components are mostly processed without requiring the corresponding Entities, the ComponentManager< T >'s interface provides iterators to Components (e.g., support for ranged-based for loop). An additional RecordIterator< T > can be used for reordering (e.g., std::sort, std::partition, etc.) the Components of a ComponentManager< T >, while reordering the data of the other two containers as well. (Originally, RecordIterator< T > and its value type, Record< T >, were contained in ComponentManager< T > due to their tight connection, but then I could not partially specialize iterator_traits.)

I need to put the swap(mage::Record< T > lhs, mage::Record< T > rhs) in the mage instead of std namespace, since both arguments are not passed as references, because RecordIterator< T >::operator* returns by value instead of reference. Currently, I do not see a possibility of returning by reference, since Record< T >'s do not pre-exist. This, unfortunately, makes the overload of RecordIterator< T >::operator-> impossible (not sure, however, which STL algorithms actually use this operator anyway).

It is not necessary to update all iterators at once in RecordIterator< T >. Only one iterator is updated via the iterator operator overloads. The other iterators are only computed upon swapping. This is much cheaper than retrieving the associated std::unordered_map< Entity, std::size_t >::iterator every iterator update (e.g., a completely sorted vector of components does not require any swaps and will thus not perform any std::unordered_map::operator[] invocations).

The erase-remove idiom is currently not possible, since associative containers (e.g., std::unordered_map) do not support std::remove/std::remove_if. I already provide a template function performing both erase and remove/remove_if on a given container, which can thus be overloaded and specialized for a ContainerManager< T > to support this as well.

My most important goals are:

  • Good performance by exploiting cache coherency, both data and instruction locality, for the most common case of processing the Components. (This depends on the actual processing of a Component as well, but that is outside the scope of the ComponentManager< T >).
  • STL compliance: the container and more importantly the associated iterators must be compatible with the functions provided in <algorithm> (e.g., work with std::partition, std::sort, std::nth_element, etc.).

I especially like to have some feedback with regard to the latter. General guidelines, best practices? (Note that I am less concerned with naming conventions: I use PascalCase for my class and method names, but deviate if similar to or needed for the std.)

Try It Online

Includes:

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <iostream>
#include <unordered_map>
#include <vector>

Placeholders:

using U32 = std::uint32_t;

template< typename T >
using AlignedVector = std::vector< T >; // placeholder

Entity:

namespace mage {

    class Entity {

    public:

        //---------------------------------------------------------------------
        // Constructors and Destructors
        //---------------------------------------------------------------------

        constexpr explicit Entity(U32 id = 0u) noexcept
            : m_id(id) {}
        constexpr Entity(const Entity& entity) noexcept = default;
        constexpr Entity(Entity&& entity) noexcept = default;
        ~Entity() = default;

        //---------------------------------------------------------------------
        // Assignment Operators
        //---------------------------------------------------------------------

        Entity& operator=(const Entity& entity) noexcept = default;
        Entity& operator=(Entity&& entity) noexcept = default;

        //---------------------------------------------------------------------
        // Member Methods
        //---------------------------------------------------------------------

        [[nodiscard]]
        constexpr U32 GetID() const noexcept {
            return m_id;
        }
        [[nodiscard]]
        constexpr explicit operator U32() const noexcept {
            return GetID();
        }

        [[nodiscard]]
        constexpr bool IsValid() const noexcept {
            return 0u != m_id;
        }
        [[nodiscard]]
        constexpr explicit operator bool() const noexcept {
            return IsValid();
        }

        [[nodiscard]]
        constexpr bool operator==(const Entity& rhs) const noexcept {
            return m_id == rhs.m_id;
        }
        [[nodiscard]]
        constexpr bool operator!=(const Entity& rhs) const noexcept {
            return m_id != rhs.m_id;
        }
        [[nodiscard]]
        constexpr bool operator<=(const Entity& rhs) const noexcept {
            return m_id <= rhs.m_id;
        }
        [[nodiscard]]
        constexpr bool operator>=(const Entity& rhs) const noexcept {
            return m_id >= rhs.m_id;
        }
        [[nodiscard]]
        constexpr bool operator<(const Entity& rhs) const noexcept {
            return m_id < rhs.m_id;
        }
        [[nodiscard]]
        constexpr bool operator>(const Entity& rhs) const noexcept {
            return m_id > rhs.m_id;
        }

        [[nodiscard]]
        std::size_t Hash() const noexcept {
            return std::hash< U32 >()(m_id);
        }

    private:

        //---------------------------------------------------------------------
        // Member Variables
        //---------------------------------------------------------------------

        U32 m_id;
    };

    static_assert(sizeof(U32) == sizeof(Entity));
}

namespace std {

    template<>
    struct hash< mage::Entity > {

        size_t operator()(const mage::Entity& entity) const {
            return entity.Hash();
        }
    };
}

ComponentManager:

namespace mage {

    //-------------------------------------------------------------------------
    // ComponentManager
    //-------------------------------------------------------------------------

    template< typename T >
    class ComponentManager {

    public:

        //---------------------------------------------------------------------
        // Class Member Types
        //---------------------------------------------------------------------

        using ComponentContainer = AlignedVector< T >;
        using EntityContainer    = AlignedVector< Entity >;
        using MappingContainer   = std::unordered_map< Entity, std::size_t >;

        using value_type
            = typename ComponentContainer::value_type;
        using size_type
            = typename ComponentContainer::size_type;
        using difference_type
            = typename ComponentContainer::difference_type;
        using reference
            = typename ComponentContainer::reference;
        using const_reference
            = typename ComponentContainer::const_reference;
        using pointer
            = typename ComponentContainer::pointer;
        using const_pointer
            = typename ComponentContainer::const_pointer;

        using iterator
            = typename ComponentContainer::iterator;
        using const_iterator
            = typename ComponentContainer::const_iterator;
        using reverse_iterator
            = typename ComponentContainer::reverse_iterator;
        using const_reverse_iterator
            = typename ComponentContainer::const_reverse_iterator;

        //---------------------------------------------------------------------
        // Friends
        //---------------------------------------------------------------------

        template< typename U >
        friend class Record;
        template< typename U >
        friend class RecordIterator;

        //---------------------------------------------------------------------
        // Constructors and Destructors
        //---------------------------------------------------------------------

        ComponentManager() = default;
        ComponentManager(const ComponentManager& manager) = default;
        ComponentManager(ComponentManager&& manager) noexcept = default;
        ~ComponentManager() = default;

        //---------------------------------------------------------------------
        // Assignment Operators
        //---------------------------------------------------------------------

        ComponentManager& operator=(const ComponentManager& manager) = default;
        ComponentManager& operator=(ComponentManager&& manager) noexcept = default;

        //---------------------------------------------------------------------
        // Member Methods: Element access
        //---------------------------------------------------------------------

        [[nodiscard]]
        bool Contains(Entity entity) const noexcept {
            return m_mapping.find(entity) != m_mapping.cend();
        }

        [[nodiscard]]
        pointer Get(Entity entity) noexcept {
            if (const auto it = m_mapping.find(entity);
                it != m_mapping.cend()) {

                return &m_components[it->second];
            }

            return nullptr;
        }

        [[nodiscard]]
        const_pointer Get(Entity entity) const noexcept {
            if (const auto it = m_mapping.find(entity);
                it != m_mapping.cend()) {

                return &m_components[it->second];
            }

            return nullptr;
        }

        [[nodiscard]]
        reference at(size_type index) {
            return m_components.at(index);
        }

        [[nodiscard]]
        const_reference at(size_type index) const {
            return m_components.at(index);
        }

        [[nodiscard]]
        reference operator[](size_type index) noexcept {
            return m_components[index];
        }

        [[nodiscard]]
        const_reference operator[](size_type index) const noexcept {
            return m_components[index];
        }

        [[nodiscard]]
        reference front() noexcept {
            return m_components.front();
        }

        [[nodiscard]]
        const_reference front() const noexcept {
            return m_components.front();
        }

        [[nodiscard]]
        reference back() noexcept {
            return m_components.back();
        }

        [[nodiscard]]
        const_reference back() const noexcept {
            return m_components.back();
        }

        [[nodiscard]]
        value_type* data() noexcept {
            return m_components.data();
        }

        [[nodiscard]]
        const value_type* data() const noexcept {
            return m_components.data();
        }

        //---------------------------------------------------------------------
        // Member Methods: Iterators
        //---------------------------------------------------------------------

        [[nodiscard]]
        iterator begin() noexcept {
            return m_components.begin();
        }

        [[nodiscard]]
        const_iterator begin() const noexcept {
            return m_components.begin();
        }

        [[nodiscard]]
        const_iterator cbegin() const noexcept {
            return m_components.cbegin();
        }

        [[nodiscard]]
        iterator end() noexcept {
            return m_components.end();
        }

        [[nodiscard]]
        const_iterator end() const noexcept {
            return m_components.end();
        }

        [[nodiscard]]
        const_iterator cend() const noexcept {
            return m_components.end();
        }

        [[nodiscard]]
        reverse_iterator rbegin() noexcept {
            return m_components.rbegin();
        }

        [[nodiscard]]
        const_reverse_iterator rbegin() const noexcept {
            return m_components.rbegin();
        }

        [[nodiscard]]
        const_reverse_iterator crbegin() const noexcept {
            return m_components.crbegin();
        }

        [[nodiscard]]
        reverse_iterator rend() noexcept {
            return m_components.rend();
        }

        [[nodiscard]]
        const_reverse_iterator rend() const noexcept {
            return m_components.rend();
        }

        [[nodiscard]]
        const_reverse_iterator crend() const noexcept {
            return m_components.crend();
        }

        //---------------------------------------------------------------------
        // Member Methods: Capacity
        //---------------------------------------------------------------------

        [[nodiscard]]
        bool empty() const noexcept {
            return m_components.empty();
        }

        [[nodiscard]]
        size_type size() const noexcept {
            return m_components.size();
        }

        [[nodiscard]]
        size_type max_size() const noexcept {
            return m_components.max_size();
        }

        void reserve(size_type new_capacity) {
            m_components.reserve(new_capacity);
            m_entities.reserve(new_capacity);
            m_mapping.reserve(new_capacity);
        }

        [[nodiscard]]
        size_type capacity() const noexcept {
            return m_components.capacity();
        }

        void shrink_to_fit() {
            m_components.shrink_to_fit();
            m_entities.shrink_to_fit();
        }

        //---------------------------------------------------------------------
        // Member Methods: Modifiers
        //---------------------------------------------------------------------

        void clear() noexcept {
            m_components.clear();
            m_entities.clear();
            m_mapping.clear();
        }

        void push_back(Entity entity, const value_type& value) {
            emplace_back(entity, value);
        }

        void push_back(Entity entity, value_type&& value) {
            emplace_back(entity, std::move(value));
        }

        template< typename... ConstructorArgsT  >
        reference emplace_back(Entity entity, ConstructorArgsT&&... args) {
            if (const auto it = m_mapping.find(entity);
                it != m_mapping.cend()) {

                return m_components[it->second];
            }

            m_mapping.emplace(entity, size());
            m_entities.push_back(entity);
            return m_components.emplace_back(
                std::forward< ConstructorArgsT >(args)...);
        }

        void pop_back() {
            m_mapping.erase(m_entities.back());
            m_components.pop_back();
            m_entities.pop_back();
        }

        void swap(ComponentManager& other) noexcept {
            std::swap(m_components, other.m_components);
            std::swap(m_entities,   other.m_entities);
            std::swap(m_mapping,    other.m_mapping);
        }

    // private: commented for std::cout illustration

        //---------------------------------------------------------------------
        // Member Variables
        //---------------------------------------------------------------------

        AlignedVector< value_type > m_components;

        AlignedVector< Entity > m_entities;

        std::unordered_map< Entity, std::size_t > m_mapping;
    };

Record:

    //-------------------------------------------------------------------------
    // Record
    //-------------------------------------------------------------------------

    template< typename T >
    class Record {

    public:

        //---------------------------------------------------------------------
        // Class Member Types
        //---------------------------------------------------------------------

        using ComponentIterator = typename ComponentManager< T >::iterator;

        //---------------------------------------------------------------------
        // Constructors and Destructors
        //---------------------------------------------------------------------

        Record() noexcept
            : m_component_it{},
            m_component_manager(nullptr) {}
        explicit Record(ComponentIterator component_it,
                        ComponentManager< T >* component_manager) noexcept
            : m_component_it(std::move(component_it)),
            m_component_manager(component_manager) {}
        Record(const Record& record) noexcept = default;
        Record(Record&& record) noexcept = default;
        ~Record() = default;

        //---------------------------------------------------------------------
        // Assignment Operators
        //---------------------------------------------------------------------

        Record& operator=(const Record& record) = delete;
        Record& operator=(Record&& record) noexcept {
            swap(record);
            return *this;
        }

        //---------------------------------------------------------------------
        // Member Methods
        //---------------------------------------------------------------------

        [[nodiscard]]
        constexpr bool operator==(const T& rhs) const noexcept {
            return *m_component_it == rhs;
        }
        [[nodiscard]]
        constexpr bool operator!=(const T& rhs) const noexcept {
            return *m_component_it != rhs;
        }
        [[nodiscard]]
        constexpr bool operator<=(const T& rhs) const noexcept {
            return *m_component_it <= rhs;
        }
        [[nodiscard]]
        constexpr bool operator>=(const T& rhs) const noexcept {
            return *m_component_it >= rhs;
        }
        [[nodiscard]]
        constexpr bool operator<(const T& rhs) const noexcept {
            return *m_component_it < rhs;
        }
        [[nodiscard]]
        constexpr bool operator>(const T& rhs) const noexcept {
            return *m_component_it > rhs;
        }

        [[nodiscard]]
        friend constexpr bool operator==(const T& lhs, const Record& rhs) noexcept {
            return lhs == *rhs.m_component_it;
        }
        [[nodiscard]]
        friend constexpr bool operator!=(const T& lhs, const Record& rhs) noexcept {
            return lhs != *rhs.m_component_it;
        }
        [[nodiscard]]
        friend constexpr bool operator<=(const T& lhs, const Record& rhs) noexcept {
            return lhs <= *rhs.m_component_it;
        }
        [[nodiscard]]
        friend constexpr bool operator>=(const T& lhs, const Record& rhs) noexcept {
            return lhs >= *rhs.m_component_it;
        }
        [[nodiscard]]
        friend constexpr bool operator<(const T& lhs, const Record& rhs) noexcept {
            return lhs < *rhs.m_component_it;
        }
        [[nodiscard]]
        friend constexpr bool operator>(const T& lhs, const Record& rhs) noexcept {
            return lhs > *rhs.m_component_it;
        }

        [[nodiscard]]
        constexpr bool operator==(const Record& rhs) const noexcept {
            return *m_component_it == *rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator!=(const Record& rhs) const noexcept {
            return *m_component_it != *rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator<=(const Record& rhs) const noexcept {
            return *m_component_it <= *rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator>=(const Record& rhs) const noexcept {
            return *m_component_it >= *rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator<(const Record& rhs) const noexcept {
            return *m_component_it < *rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator>(const Record& rhs) const noexcept {
            return *m_component_it > *rhs.m_component_it;
        }

        void swap(Record& other) noexcept {
        assert(nullptr != m_component_manager);
        assert(m_component_manager == other.m_component_manager);
        assert(m_component_manager->end() != m_component_it);
        assert(m_component_manager->end() != other.m_component_it);

        const std::size_t index1 = m_component_it - m_component_manager->m_components.begin();
        auto& entity1            = m_component_manager->m_entities[index1];
        auto& mapping1           = m_component_manager->m_mapping[entity1];

        const std::size_t index2 = other.m_component_it - m_component_manager->m_components.begin();
        auto& entity2            = m_component_manager->m_entities[index2];
        auto& mapping2           = m_component_manager->m_mapping[entity2];

        std::swap(*m_component_it, *other.m_component_it);
        std::swap(entity1, entity2);
        std::swap(mapping1, mapping2);
    }

    private:

        //---------------------------------------------------------------------
        // Member Variables
        //---------------------------------------------------------------------

        ComponentIterator m_component_it;
        ComponentManager< T >* m_component_manager;
    };

RecordIterator:

    //-------------------------------------------------------------------------
    // RecordIterator
    //-------------------------------------------------------------------------

    template< typename T >
    class RecordIterator {

    public:

        //---------------------------------------------------------------------
        // Class Member Types
        //---------------------------------------------------------------------

        using ComponentIterator = typename ComponentManager< T >::iterator;

        //---------------------------------------------------------------------
        // Constructors and Destructors
        //---------------------------------------------------------------------

        RecordIterator() noexcept
            : m_component_it{},
            m_component_manager(nullptr) {}
        explicit RecordIterator(ComponentIterator component_it,
                                ComponentManager< T >* component_manager) noexcept
            : m_component_it(std::move(component_it)), 
            m_component_manager(component_manager) {}
        RecordIterator(const RecordIterator& it) noexcept = default;
        RecordIterator(RecordIterator&& it) noexcept = default;
        ~RecordIterator() = default;

        //---------------------------------------------------------------------
        // Assignment Operators
        //---------------------------------------------------------------------

        RecordIterator& operator=(const RecordIterator& it) noexcept = default;
        RecordIterator& operator=(RecordIterator&& it) noexcept = default;

        //---------------------------------------------------------------------
        // Member Methods
        //---------------------------------------------------------------------

        Record< T > operator*() noexcept {
            return Record< T >(m_component_it, m_component_manager);
        }
        Record< T > operator*() const noexcept {
            return Record< T >(m_component_it, m_component_manager);
        }

        [[nodiscard]]
        Record< T > operator[](std::size_t n) noexcept {
            return Record< T >(m_component_it + n, m_component_manager);
        }
        [[nodiscard]]
        Record< T > operator[](std::size_t n) const noexcept {
            return Record< T >(m_component_it + n, m_component_manager);
        }

        [[nodiscard]]
        std::size_t operator-(const RecordIterator& it) const noexcept {
            return m_component_it - it.m_component_it;
        }

        [[nodiscard]]
        const RecordIterator operator+(std::size_t n) const noexcept {
            return RecordIterator(m_component_it + n, m_component_manager);
        }
        [[nodiscard]]
        const RecordIterator operator-(std::size_t n) const noexcept {
            return RecordIterator(m_component_it - n, m_component_manager);
        }

        [[nodiscard]]
        friend const RecordIterator operator+(std::size_t n, const RecordIterator& it) noexcept {
            return it + n;
        }

        RecordIterator& operator++() noexcept {
            ++m_component_it;
            return *this;
        }
        RecordIterator& operator--() noexcept {
            --m_component_it;
            return *this;
        }

        [[nodiscard]]
        friend const RecordIterator operator++(const RecordIterator& it) noexcept {
            return RecordIterator(it.m_component_it + 1u, it.m_component_manager);
        }
        [[nodiscard]]
        friend const RecordIterator operator--(const RecordIterator& it) noexcept {
            return RecordIterator(it.m_component_it - 1u, it.m_component_manager);
        }

        RecordIterator& operator+=(std::size_t n) noexcept {
            m_component_it += n;
            return *this;
        }
        RecordIterator& operator-=(std::size_t n) noexcept {
            m_component_it -= n;
            return *this;
        }

        [[nodiscard]]
        constexpr bool operator==(const RecordIterator& rhs) const noexcept {
            return m_component_it == rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator!=(const RecordIterator& rhs) const noexcept {
            return m_component_it != rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator<=(const RecordIterator& rhs) const noexcept {
            return m_component_it <= rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator>=(const RecordIterator& rhs) const noexcept {
            return m_component_it >= rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator<(const RecordIterator& rhs) const noexcept {
            return m_component_it < rhs.m_component_it;
        }
        [[nodiscard]]
        constexpr bool operator>(const RecordIterator& rhs) const noexcept {
            return m_component_it > rhs.m_component_it;
        }

    private:

        ComponentIterator m_component_it;
        ComponentManager< T >* m_component_manager;
    };

Miscellaneous:

    template< typename T >
    [[nodiscard]]
    inline RecordIterator< T > RecordBegin(ComponentManager< T >& manager) noexcept {
        return RecordIterator< T >(manager.begin(), &manager);
    }

    template< typename T >
    [[nodiscard]]
    inline RecordIterator< T > RecordEnd(ComponentManager< T >& manager) noexcept {
        return RecordIterator< T >(manager.end(), &manager);
    }

    template< typename T >
    void swap(mage::Record< T > lhs, mage::Record< T > rhs) noexcept {
        lhs.swap(rhs);
    }
}

namespace std {

    template< typename T >
    struct iterator_traits< mage::RecordIterator< T > > {

    public:

        using value_type        = mage::Record< T >;
        using reference_type    = mage::Record< T >&; // not sure?
        using pointer_type      = mage::Record< T >*; // not sure?
        using difference_type   = ptrdiff_t;
        using iterator_category = random_access_iterator_tag;
    };
}

Main:

int main() {
    mage::ComponentManager< float > manager;
    manager.emplace_back(mage::Entity(5u), 5.0f);
    manager.emplace_back(mage::Entity(4u), 4.0f);
    manager.emplace_back(mage::Entity(3u), 3.0f);
    manager.emplace_back(mage::Entity(2u), 2.0f);
    manager.emplace_back(mage::Entity(1u), 1.0f);

    for (auto& c : manager.m_components) {
        std::cout << c;
    }
    std::cout << std::endl;
    for (auto& e : manager.m_entities) {
        std::cout << U32(e);
    }
    std::cout << std::endl;
    for (auto& [e, i] : manager.m_mapping) {
        std::cout << '(' << U32(e) << "->" << i << ')';
    }
    std::cout << std::endl;

    std::sort(RecordBegin(manager), RecordEnd(manager));

    for (auto& c : manager.m_components) {
        std::cout << c;
    }
    std::cout << std::endl;
    for (auto& e : manager.m_entities) {
        std::cout << U32(e);
    }
    std::cout << std::endl;
    for (auto& [e, i] : manager.m_mapping) {
        std::cout << '(' << U32(e) << "->" << i << ')';
    }
    std::cout << std::endl;

    return 0;
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$
  • I guess you've already checked the iterator requirements, but just in case; it's basically implementing everything on this page (and the "named requirements" pages linked from it): https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator

  • The various iterator_traits typedefs are usually defined directly in the iterator class. The unspecialized version of iterator_traits just redirects to typedefs defined in the class.

  • These types should then be used for the return values of the various iterator functions, e.g.

    difference_type operator-(const RecordIterator& it) const noexcept {
        return m_component_it - it.m_component_it;
    }
    
  • Note: this originally returned std::size_t. However, the difference_type must be signed (std::ptrdiff_t is correct). The other mathematical operations have the same issue.

  • Unfortunately, we actually need pointer and reference typedefs, not pointer_type and reference_type.

  • I don't think it's possible to fulfill the exact requirements for the reference type (ForwardIterator requires it to be T& or T const& where T is value_type). Defining it as Record<T> should work correctly in most cases though.

  • For pointer, you might simply define it as void, leave the arrow operator undefined, and see if anything breaks. This answer on stackoverflow implies that reference and pointer aren't used inside the algorithms themselves, so you should be ok. Perhaps someone else with more knowledge can weigh in on this.


  • Maybe the Record class should be lighter weight - handling swap and move only, and providing a means to access the underlying component. At the moment Record rather assumes that the components will be able to overload the relevant comparison operator, and that the algorithms using them will only need comparison operators. I could see, e.g. a LightingComponent needing to sort lights by distance, or partition them with frustum culling, and might require custom comparators. Being able to access the underlying component from Record would allow this, as well as use with a wider range of algorithms.

  • Is it really necessary to allow an "invalid" Entity to exist?
\$\endgroup\$
4
  • \$\begingroup\$ Thank you for the detailed feedback! You are absolutely right that Record should be more lightweight. Furthermore, invalid Entities are not needed (originally, I thought to use invalid Entities for my own garbage collection, but this flagging should be handled by the Components instead. I tried to integrate all of this feedback in the following Try It Online. \$\endgroup\$
    – Matthias
    Commented Oct 29, 2018 at 19:16
  • \$\begingroup\$ Defining the various iterator_traits typedefs in the iterator class itself (and the fact that the std can handle this) is wonderful, since this allows to move Record and RecordIterator back to ComponentManager. Similarly, for the RecordBegin and RecordEnd functions, which is more convenient for a user. \$\endgroup\$
    – Matthias
    Commented Oct 29, 2018 at 19:19
  • \$\begingroup\$ Unfortunately, the latter is still not possible in a general way (i.e. template) for due to the swap. Try It Online SFINAE can probably provide a way around this (i.e. additional unique typedef inside record). \$\endgroup\$
    – Matthias
    Commented Oct 29, 2018 at 19:39
  • \$\begingroup\$ I found a solution for the latter using inline friend: Try It Online \$\endgroup\$
    – Matthias
    Commented Nov 13, 2018 at 10:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.