fortytoo
fortytoo

Reputation: 462

unordered_map with values of containers holding different trivial types

I am trying to make an entity component system with an interface like so:

int main() {
    ecs::Registry registry;

    // Add int, float, double components to the registry
    registry.createComponents<int, float, double>();
    
    // You don't have to supply all of the components at once
    registry.createComponents<char>();

    // Create an entity with int and float components, initialize them to 1 and 1.3f
    ecs::Entity entity = registry.createEntity<int, float>(1, 1.3f);

    // Add a double and a char component to the entity, set the double to 4.8
    entity.addComponents<double, char>(4.8);

    // Set the char component of entity to 'a'
    // You don't have to set all the components in the addComponents function. Components not set will be default initialized
    entity.set<char>('a');

    // Access a component with entity.get<Component>()
    std::cout << entity.get<int>() << std::endl;
    std::cout << entity.get<float>() << std::endl;
    std::cout << entity.get<double>() << std::endl;
    std::cout << entity.get<char>() << std::endl;

    // Remove given components
    entity.removeComponents<int, float, double>();

    // Print number of components
    std::cout << "number of components: " << entity.componentCount() << std::endl;

    // Char component still valid after other components deleted
    std::cout << entity.get<char>() << std::endl;

    // Delete entity
    registry.deleteEntity(entity);

    // Remove all given components from the registry
    registry.deleteComponents<int, float, double, char>();
}

The Registry holds all the data, Entity merely holds its ID and a reference to the Registry. The problem is, inside the Registry, I want to have arrays of ints, floats, doubles, basically any trivial type. And I want to be able to access them through templated functions, and the only way I can envision this system working is by having an unordered_map with keys of type_index and vectors of the type specified by the type_index. But I can't have an unordered_map with differently templated values. I made a really dumb class that goes against every good C++ practice you could follow that would just hold the types using a vector of uint8_ts, but that is.. bad. Not good. Very messy code and an interface that makes me want to cry.

How do I have an unordered_map that holds contiguous, homogeneous containers of different trivial types?

Upvotes: 0

Views: 121

Answers (1)

david dragilev
david dragilev

Reputation: 81

You can hold each type in a separate unordered_map. That way you can support any data type. Here's a possible implementation of Registry and Entity classes:

#include <unordered_map>
#include <vector>
#include <algorithm>
#include <typeinfo>
#include <typeindex>
#include <memory>

struct icontainer
{
    virtual ~icontainer() = default;
};
template <typename T>
struct container : icontainer
{
  
    std::unordered_map<int, //entry_id
        T> m_entryIdToValue; //value
};
struct Entity;

struct Registry
{
    template <typename First>
    void createComponents()
    {
        std::unique_ptr<icontainer> up{ new container<First> };
        m_registeredContainers[std::type_index(typeid(First))] = std::move(up);
    }

    template <typename First, typename Second, typename... Types>
    void createComponents()
    {
        createComponents<First>();
        createComponents<Second, Types...>();
    }

    template <typename... Types>
    Entity createEntity(Types ...values);

    std::unordered_map<std::type_index, std::unique_ptr<icontainer>> m_registeredContainers;
    std::size_t m_entityId = 0;
};

struct Entity
{
     Entity(Registry *registry, std::size_t id) :m_registry(registry), m_nId(id){}

    template <typename First>
    void addComponents(const First &value)
    {
        get<First>() = value;
    }

    template <typename First, typename Second, typename... Types >
    void addComponents(const First& first_value, const Second& second_value, Types ...values)
    {
        addComponents<First>(first_value);
        addComponents<Second, Types...>(second_value, values...);
    }

    template <typename T>
    T& get()
    {
        icontainer& c = *m_registry->m_registeredContainers[std::type_index(typeid(T))];
        return static_cast<container<T>*>(&c)->m_entryIdToValue[m_nId];
    }

    std::size_t m_nId = 0;
    Registry* m_registry = nullptr;
};

template <typename... Types>
Entity Registry::createEntity(Types ...values)
{
    Entity e(this, m_entityId++);
    e.addComponents(std::forward<Types>(values)...);
    return e;
}

Here's a creation of 2 entities from a repository:

int main()
{
    Registry r;
    r.createComponents<int,float,double,char>();
    Entity e = r.createEntity<int, float>(1,1.1f);
    e.addComponents<double>(2.2);

    std::cout << "entity 1 int " << e.get<int>() << std::endl;
    std::cout << "entity 1 float " << e.get<float>() << std::endl;
    std::cout << "entity 1 double " << e.get<double>() << std::endl;

    Entity e2 = r.createEntity<int, char>(5, 'A');
    std::cout << "entity 2 int " << e2.get<int>() << std::endl;
    std::cout << "entity 2 char " << e2.get<char>() << std::endl;


    return 0;
  
}

Output:

entity 1 int 1

entity 1 float 1.1

entity 1 double 2.2

entity 2 int 5

entity 2 char A

  • The code was compiled and tested on Visual Studio 2019.

Upvotes: 1

Related Questions