Reputation: 462
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 int
s, float
s, double
s, 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 vector
s 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_t
s, 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
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
Upvotes: 1