From 71da59cd75f87f347c6d70224ca7f8856e665d4d Mon Sep 17 00:00:00 2001 From: NukeBird Date: Mon, 17 Feb 2025 22:35:43 +0300 Subject: [PATCH] Bye-bye typeid --- zecsy.hpp | 310 ++++++++++++++++++++++++++---------------------------- 1 file changed, 152 insertions(+), 158 deletions(-) diff --git a/zecsy.hpp b/zecsy.hpp index ceaf311..a798e7e 100644 --- a/zecsy.hpp +++ b/zecsy.hpp @@ -7,18 +7,16 @@ #include #include #include -#include #include +#include namespace zecsy { using entity_id = uint64_t; /* - * unordered_set is also an option. But probably sorting ids may be - * beneficial for queries, because it might increase the chance of reusing - * cpu cache (aka "required components were close to each other"). - * Definitely should play around with it + * Using std::set for entities_set to maintain sorted order, which can + * improve cache locality during queries and iterations. */ using entities_set = std::set; @@ -54,26 +52,132 @@ namespace zecsy void remove(entity_id e); private: - using comp_index = std::type_index; - using comp_to_entities_set = - std::unordered_map; - using entity_to_index = std::unordered_map; + using comp_id = size_t; - using comp_to_entity_dict = - std::unordered_map; + struct component_pool + { + std::vector data; + std::queue free_list; // Reusable indices + std::unordered_map entity_to_index; + std::unordered_map index_to_entity; + }; - using comp_to_storage = - std::unordered_map>; + std::unordered_map pools; - using comp_to_reusable_ids = - std::unordered_map>; + template + static comp_id get_component_id() + { + static comp_id id = next_component_id++; + return id; + } - comp_to_entities_set entities_dict; - comp_to_entity_dict indices_dict; - comp_to_storage storage_dict; - comp_to_reusable_ids reusable_id_queues; + static comp_id next_component_id; }; + inline component_storage::comp_id component_storage::next_component_id = 0; + + template + inline bool component_storage::has(entity_id e) const + { + auto id = get_component_id(); + if(pools.contains(id)) + { + return pools.at(id).entity_to_index.contains(e); + } + return false; + } + + template + inline T& component_storage::get(entity_id e) + { + auto id = get_component_id(); + if(!has(e)) + { + throw std::runtime_error( + std::format("Entity #{} doesn't have {}", e, typeid(T).name())); + } + + auto& pool = pools.at(id); + auto index = pool.entity_to_index.at(e); + return *reinterpret_cast(&pool.data[index * sizeof(T)]); + } + + template + inline void component_storage::set(entity_id e, const T& comp) + { + auto id = get_component_id(); + auto& pool = pools[id]; + + size_t index; + if(!pool.free_list.empty()) + { + index = pool.free_list.front(); + pool.free_list.pop(); + } + else + { + index = pool.data.size() / sizeof(T); + pool.data.resize(pool.data.size() + sizeof(T)); + } + + new(&pool.data[index * sizeof(T)]) T(comp); + pool.entity_to_index[e] = index; + pool.index_to_entity[index] = e; + } + + template + inline void component_storage::set(entity_id e) + { + set(e, T{}); + } + + template + inline void component_storage::remove(entity_id e) + { + auto id = get_component_id(); + if(!has(e)) + { + return; + } + + auto& pool = pools[id]; + auto index = pool.entity_to_index[e]; + pool.free_list.push(index); + pool.entity_to_index.erase(e); + pool.index_to_entity.erase(index); + } + + template + inline bool component_storage::has(entity_id e) const + { + return has(e) && has(e) && (has(e) && ...); + } + + template + inline void component_storage::set(entity_id e) + { + set(e, First{}); + set(e, Second{}); + (set(e, Rest{}), ...); + } + + template + 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 + inline void component_storage::remove(entity_id e) + { + remove(e); + remove(e); + (remove(e), ...); + } + class system_scheduler final { public: @@ -94,6 +198,32 @@ namespace zecsy std::vector systems; }; + inline void system_scheduler::add_system(float freq, auto&& func) + { + systems.emplace_back(1.0f / freq, 0.0f, + std::forward(func)); + } + + inline void system_scheduler::add_system(int freq, auto&& func) + { + add_system(float(freq), func); + } + + inline void system_scheduler::update(float dt) + { + dt = std::max(0.0f, dt); + + for(auto& s: systems) + { + s.accumulator += dt; + while(s.accumulator >= s.interval) + { + s.callback(dt); + s.accumulator -= s.interval; + } + } + } + class world final { public: @@ -129,7 +259,6 @@ namespace zecsy { auto id = ++entity_counter; alive_entities.emplace(id); - return id; } @@ -143,31 +272,10 @@ namespace zecsy return alive_entities.contains(e); } - template - inline bool component_storage::has(entity_id e) const + template + inline bool world::has(entity_id e) const { - if(entities_dict.contains(typeid(T))) - { - return entities_dict.at(typeid(T)).contains(e); - } - return false; - } - - template - inline T& component_storage::get(entity_id e) - { - if(!has(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(&storage_dict[typeid(T)][0]); - ptr += index; - - return *ptr; + return storage.has(e); } template @@ -176,43 +284,6 @@ namespace zecsy return storage.get(e); } - template - 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(&storage[0]); - new(ptr + index) T(comp); - - indices_dict[typeid(T)][e] = index; - } - - template - inline void component_storage::set(entity_id e) - { - set(e, T{}); - } - template inline void world::set(entity_id e) { @@ -225,63 +296,12 @@ namespace zecsy storage.set(e, comps...); } - template - inline void component_storage::remove(entity_id e) - { - if(!has(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 inline void world::remove(entity_id e) { storage.remove(e); } - template - inline bool world::has(entity_id e) const - { - return storage.has(e); - } - - template - inline bool component_storage::has(entity_id e) const - { - return has(e) && has(e) && (has(e) && ...); - } - - template - inline void component_storage::set(entity_id e) - { - set(e, First{}); - set(e, Second{}); - (set(e, Rest{}), ...); - } - - template - 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 - inline void component_storage::remove(entity_id e) - { - remove(e); - remove(e); - (remove(e), ...); - } - template inline void world::query(auto&& system) { @@ -293,30 +313,4 @@ namespace zecsy } } } - - inline void system_scheduler::add_system(float freq, auto&& func) - { - systems.emplace_back(1.0f / freq, 0.0f, - std::forward(func)); - } - - inline void system_scheduler::add_system(int freq, auto&& func) - { - add_system(float(freq), func); - } - - inline void system_scheduler::update(float dt) - { - dt = std::max(0.0f, dt); - - for(auto& s: systems) - { - s.accumulator += dt; - while(s.accumulator >= s.interval) - { - s.callback(dt); - s.accumulator -= s.interval; - } - } - } }; // namespace zecsy