#pragma once #include <catch2/internal/catch_console_colour.hpp> #include <cstdint> #include <format> #include <queue> #include <stdexcept> #include <typeindex> #include <unordered_map> #include <unordered_set> namespace zecsy { using entity_id = uint64_t; using entities_set = std::unordered_set<entity_id>; 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; }; class component_storage { public: template<typename T> bool has(entity_id e) const; template<typename First, typename Second, typename... Rest> 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> void set(entity_id e); template<typename First, typename Second, typename... Rest> void set(entity_id e, const First& comp0, const Second& comp1, const Rest&... rest_comps); template<typename T> void remove(entity_id e); template<typename First, typename Second, typename... Rest> void remove(entity_id e); private: using comp_index = std::type_index; using comp_to_entities_set = std::unordered_map<comp_index, entities_set>; 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_entities_set entities_dict; comp_to_entity_dict indices_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: entities_set alive_entities; 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; alive_entities.emplace(id); return entity(this, id); } inline void world::destroy_entity(entity_id e) { alive_entities.erase(e); } inline bool world::is_alive(entity_id e) const { return alive_entities.contains(e); } template<typename T> inline bool component_storage::has(entity_id e) const { if(entities_dict.contains(typeid(T))) { return entities_dict.at(typeid(T)).contains(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 = indices_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) { entities_dict[typeid(T)].emplace(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); indices_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); indices_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; } entities_dict[typeid(T)].erase(e); reusable_id_queues[typeid(T)].push(indices_dict[typeid(T)][e]); indices_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> 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> inline void component_storage::set(entity_id e) { set(e, First{}); set(e, Second{}); (set(e, Rest{}), ...); } template<typename First, typename Second, typename... Rest> 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), ...); } template<typename First, typename Second, typename... Rest> inline void component_storage::remove(entity_id e) { remove<First>(e); remove<Second>(e); (remove<Rest>(e), ...); } };