zecsy/zecsy.hpp

323 lines
7.6 KiB
C++

#pragma once
#include <catch2/internal/catch_console_colour.hpp>
#include <cstdint>
#include <bitset>
#include <format>
#include <queue>
#include <stdexcept>
#include <typeindex>
#include <unordered_map>
#ifndef MAX_ZECSY_ENTITIES
#define MAX_ZECSY_ENTITIES 65536
#endif // !MAX_ZECSY_ENTITIES
namespace zecsy
{
using entity_id = uint64_t;
class entity final
{
public:
entity(class world* w, entity_id id);
entity() = default;
operator entity_id() const;
bool is_alive() const;
template<typename... T>
bool has() const;
template<typename T>
T& get();
template<typename... T>
void set();
template<typename... T>
void set(const T&... comps);
template<typename T>
void remove();
private:
entity_id id = 0;
class world* w = nullptr;
};
using zecsy_bits = std::bitset<MAX_ZECSY_ENTITIES + 1>;
class component_storage
{
public:
template<typename T>
bool has(entity_id e) const;
template<typename First, typename Second, typename... Rest>
requires(sizeof...(Rest) >= 0)
bool has(entity_id e) const;
template<typename T>
T& get(entity_id e);
template<typename T>
void set(entity_id e);
template<typename T>
void set(entity_id e, const T& comp);
template<typename First, typename Second, typename... Rest>
requires(sizeof...(Rest) >= 0)
void set(entity_id e);
template<typename First, typename Second, typename... Rest>
requires(sizeof...(Rest) >= 0)
void set(entity_id e, const First& comp0, const Second& comp1,
const Rest&... rest_comps);
template<typename T>
void remove(entity_id e);
private:
using comp_index = std::type_index;
using comp_to_bitset = std::unordered_map<comp_index, zecsy_bits>;
using entity_to_index = std::unordered_map<entity_id, size_t>;
using comp_to_entity_dict = std::unordered_map<comp_index,
entity_to_index>;
using comp_to_storage = std::unordered_map<comp_index,
std::vector<uint8_t>>;
using comp_to_reusable_ids = std::unordered_map<comp_index,
std::queue<size_t>>;
comp_to_bitset bitset_dict;
comp_to_entity_dict entity_dict;
comp_to_storage storage_dict;
comp_to_reusable_ids reusable_id_queues;
};
class world final
{
public:
entity make_entity();
void destroy_entity(entity_id e);
bool is_alive(entity_id e) const;
template<typename... T>
bool has(entity_id e) const;
template<typename T>
T& get(entity_id e);
template<typename... T>
void set(entity_id e);
template<typename... T>
void set(entity_id e, const T&... comps);
template<typename T>
void remove(entity_id e);
private:
zecsy_bits entities_bitset;
entity_id entity_counter = 0;
component_storage storage;
};
inline entity::entity(class world* w, entity_id id): w(w), id(id)
{
}
inline entity::operator entity_id() const
{
return id;
}
inline bool entity::is_alive() const
{
return w && w->is_alive(id);
}
inline entity world::make_entity()
{
auto id = ++entity_counter;
if(id > MAX_ZECSY_ENTITIES)
{
throw std::runtime_error(std::format("Entity id {} exceeds "
"MAX_ZECSY_ENTITIES ({})", id, MAX_ZECSY_ENTITIES));
}
entities_bitset.set(id);
return entity(this, id);
}
inline void world::destroy_entity(entity_id e)
{
entities_bitset.reset(e);
}
inline bool world::is_alive(entity_id e) const
{
return entities_bitset.test(e);
}
template<typename T>
inline bool component_storage::has(entity_id e) const
{
if(bitset_dict.contains(typeid(T)))
{
return bitset_dict.at(typeid(T)).test(e);
}
return false;
}
template<typename T>
inline T& component_storage::get(entity_id e)
{
if(!has<T>(e))
{
throw std::runtime_error(std::format("Entity #{} doesn't have {}",
e, typeid(T).name()));
}
auto index = entity_dict[typeid(T)].at(e);
auto* ptr = reinterpret_cast<T*>(&storage_dict[typeid(T)][0]);
ptr += index;
return *ptr;
}
template<typename... T>
inline bool entity::has() const
{
return w->has<T...>(id);
}
template<typename T>
inline T& world::get(entity_id e)
{
return storage.get<T>(e);
}
template<typename T>
inline T& entity::get()
{
return w->get<T>(id);
}
template<typename... T>
inline void entity::set()
{
(set(T{}), ...);
}
template<typename T>
inline void component_storage::set(entity_id e, const T& comp)
{
bitset_dict[typeid(T)].set(e);
auto& storage = storage_dict[typeid(T)];
auto& reusable_ids = reusable_id_queues[typeid(T)];
if(reusable_ids.empty())
{
size_t T_size = sizeof(T);
size_t old_size = storage.size();
storage.resize(old_size + T_size);
void* ptr = &storage[0] + old_size;
new(ptr) T(comp);
entity_dict[typeid(T)][e] = old_size / T_size;
return;
}
auto index = reusable_ids.front();
reusable_ids.pop();
auto ptr = reinterpret_cast<T*>(&storage[0]);
new(ptr + index) T(comp);
entity_dict[typeid(T)][e] = index;
}
template<typename... T>
inline void world::set(entity_id e)
{
storage.set<T...>(e);
}
template<typename... T>
inline void world::set(entity_id e, const T&... comps)
{
storage.set(e, comps...);
}
template<typename... T>
inline void entity::set(const T&... comps)
{
w->set(id, comps...);
}
template<typename T>
inline void component_storage::remove(entity_id e)
{
if(!has<T>(e))
{
return;
}
bitset_dict[typeid(T)].reset(e);
reusable_id_queues[typeid(T)].push(entity_dict[typeid(T)][e]);
entity_dict[typeid(T)].erase(e);
}
template<typename T>
inline void world::remove(entity_id e)
{
storage.remove<T>(e);
}
template<typename T>
inline void entity::remove()
{
w->remove<T>(id);
}
template<typename... T>
inline bool world::has(entity_id e) const
{
return storage.has<T...>(e);
}
template<typename First, typename Second, typename... Rest>
requires(sizeof...(Rest) >= 0)
inline bool component_storage::has(entity_id e) const
{
return has<First>(e) && has<Second>(e) && (has<Rest>(e) && ...);
}
template<typename First, typename Second, typename... Rest>
requires(sizeof...(Rest) >= 0)
inline void component_storage::set(entity_id e)
{
set(e, First{});
set(e, Second{});
(set(e, Rest{}), ...);
}
template<typename First, typename Second, typename... Rest>
requires(sizeof...(Rest) >= 0)
inline void component_storage::set(entity_id e, const First& comp0, const Second& comp1,
const Rest&... rest_comps)
{
set(e, comp0);
set(e, comp1);
(set(e, rest_comps), ...);
}
};