From bda170bf6dcfb5640f78b5d0166edc82c2d825c3 Mon Sep 17 00:00:00 2001 From: NukeBird Date: Thu, 20 Feb 2025 17:49:49 +0300 Subject: [PATCH] Archetypes now based on bitsets (performance++) --- tests/zecsy.cpp | 20 +++--- zecsy.hpp | 164 ++++++++++++++++++++---------------------------- 2 files changed, 77 insertions(+), 107 deletions(-) diff --git a/tests/zecsy.cpp b/tests/zecsy.cpp index 60dbfc0..11f187e 100644 --- a/tests/zecsy.cpp +++ b/tests/zecsy.cpp @@ -604,8 +604,8 @@ namespace // Benchmark entity counts constexpr int SMALL = 1'000; - constexpr int MEDIUM = 5'000; - constexpr int LARGE = 10'000; + constexpr int MEDIUM = 10'000; + constexpr int LARGE = 50'000; } // namespace TEST_CASE("Core operations benchmarks", "[benchmark]") @@ -625,7 +625,7 @@ TEST_CASE("Core operations benchmarks", "[benchmark]") }); }; - BENCHMARK_ADVANCED("Create entities [5000]")( + BENCHMARK_ADVANCED("Create entities [10000]")( Catch::Benchmark::Chronometer meter) { meter.measure( @@ -640,7 +640,7 @@ TEST_CASE("Core operations benchmarks", "[benchmark]") }); }; - BENCHMARK_ADVANCED("Create entities [10000]")( + BENCHMARK_ADVANCED("Create entities [50000]")( Catch::Benchmark::Chronometer meter) { meter.measure( @@ -672,7 +672,7 @@ TEST_CASE("Core operations benchmarks", "[benchmark]") }); }; - BENCHMARK_ADVANCED("Create entities with components [5000]")( + BENCHMARK_ADVANCED("Create entities with components [10000]")( Catch::Benchmark::Chronometer meter) { meter.measure( @@ -689,7 +689,7 @@ TEST_CASE("Core operations benchmarks", "[benchmark]") }); }; - BENCHMARK_ADVANCED("Create entities with components [10000]")( + BENCHMARK_ADVANCED("Create entities with components [50000]")( Catch::Benchmark::Chronometer meter) { meter.measure( @@ -730,7 +730,7 @@ TEST_CASE("Component operations benchmarks", "[benchmark]") }); }; - BENCHMARK_ADVANCED("Add component to existing entities [5000]")( + BENCHMARK_ADVANCED("Add component to existing entities [10000]")( Catch::Benchmark::Chronometer meter) { world w; @@ -751,7 +751,7 @@ TEST_CASE("Component operations benchmarks", "[benchmark]") }); }; - BENCHMARK_ADVANCED("Add component to existing entities [10000]")( + BENCHMARK_ADVANCED("Add component to existing entities [50000]")( Catch::Benchmark::Chronometer meter) { world w; @@ -795,7 +795,7 @@ TEST_CASE("Component operations benchmarks", "[benchmark]") }); }; - BENCHMARK_ADVANCED("Remove component from entities [5000]")( + BENCHMARK_ADVANCED("Remove component from entities [10000]")( Catch::Benchmark::Chronometer meter) { world w; @@ -818,7 +818,7 @@ TEST_CASE("Component operations benchmarks", "[benchmark]") }); }; - BENCHMARK_ADVANCED("Remove component from entities [10000]")( + BENCHMARK_ADVANCED("Remove component from entities [50000]")( Catch::Benchmark::Chronometer meter) { world w; diff --git a/zecsy.hpp b/zecsy.hpp index 1910148..117aef0 100644 --- a/zecsy.hpp +++ b/zecsy.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -10,6 +11,10 @@ #include #include +#ifndef ZECSY_MAX_COMPONENTS + #define ZECSY_MAX_COMPONENTS 32 +#endif // !ZECSY_MAX_COMPONENTS + namespace zecsy { using entity_id = uint64_t; @@ -79,8 +84,9 @@ namespace zecsy private: using comp_id = size_t; - std::set alive_entities; - std::unordered_map> entity_to_comps; + using zecsy_bits = std::bitset; + + std::unordered_map entity_to_comps; entity_id entity_counter = 0; size_t query_archetypes_checked = 0; @@ -96,16 +102,10 @@ namespace zecsy std::unordered_map pools; - using archetype_signature = std::vector; + using archetype_signature = zecsy_bits; using entity_group = std::set; - struct archetype_hash - { - size_t operator()(const archetype_signature& vec) const; - }; - - std::unordered_map - archetypes; + std::unordered_map archetypes; template static comp_id get_component_id(); @@ -113,17 +113,6 @@ namespace zecsy static comp_id next_component_id; }; - inline size_t world::archetype_hash::operator()( - const world::archetype_signature& vec) const - { - size_t seed = vec.size(); - for(const auto& id: vec) - { - seed ^= id + 0x9e3779b9 + (seed << 6) + (seed >> 2); - } - return seed; - } - template inline world::comp_id world::get_component_id() { @@ -135,12 +124,12 @@ namespace zecsy inline size_t world::components_in_entity(entity_id e) const { - return entity_to_comps.contains(e) ? entity_to_comps.at(e).size() : 0; + return entity_to_comps.contains(e) ? entity_to_comps.at(e).count() : 0; } inline size_t world::entity_count() const { - return alive_entities.size(); + return entity_to_comps.size(); } inline size_t world::total_component_count() const @@ -181,10 +170,9 @@ namespace zecsy inline entity_id world::make_entity() { auto id = ++entity_counter; - alive_entities.emplace(id); entity_to_comps[id] = {}; - std::vector key; + archetype_signature key; auto& group = archetypes[key]; group.emplace(id); @@ -193,26 +181,26 @@ namespace zecsy inline void world::destroy_entity(entity_id e) { - alive_entities.erase(e); + auto archetype = entity_to_comps[e]; - auto& comp_set = entity_to_comps[e]; - std::vector key(comp_set.begin(), comp_set.end()); - - auto& group = archetypes[key]; + auto& group = archetypes[archetype]; group.erase(e); - if(archetypes[key].empty()) + if(archetypes[archetype].empty()) { - archetypes.erase(key); + archetypes.erase(archetype); } - for(comp_id id: comp_set) + for(int id = 0; id < ZECSY_MAX_COMPONENTS; ++id) { - auto& pool = pools[id]; - auto index = pool.entity_to_index[e]; - pool.entity_to_index.erase(e); - pool.index_to_entity.erase(index); - pool.free_list.emplace_back(index); + if(archetype.test(id)) + { + auto& pool = pools[id]; + auto index = pool.entity_to_index[e]; + pool.entity_to_index.erase(e); + pool.index_to_entity.erase(index); + pool.free_list.emplace_back(index); + } } entity_to_comps.erase(e); @@ -220,7 +208,7 @@ namespace zecsy inline bool world::is_alive(entity_id e) const { - return alive_entities.contains(e); + return entity_to_comps.contains(e); } template @@ -228,7 +216,7 @@ namespace zecsy { if(entity_to_comps.contains(e)) { - return entity_to_comps.at(e).contains(get_component_id()); + return entity_to_comps.at(e).test(get_component_id()); } return false; } @@ -270,26 +258,20 @@ namespace zecsy pool.entity_to_index[e] = index; pool.index_to_entity[index] = e; - auto& comp_set = entity_to_comps[e]; - std::set old_set = comp_set; - auto [it, inserted] = comp_set.insert(id); + auto& archetype = entity_to_comps[e]; + auto old_archetype = archetype; + archetype.set(id); - if(inserted) + auto& group = archetypes[old_archetype]; + group.erase(e); + + if(archetypes[old_archetype].empty()) { - std::vector old_key(old_set.begin(), old_set.end()); - - auto& group = archetypes[old_key]; - group.erase(e); - - if(archetypes[old_key].empty()) - { - archetypes.erase(old_key); - } - - std::vector new_key(comp_set.begin(), comp_set.end()); - archetypes[new_key].emplace(e); + archetypes.erase(old_archetype); } - } + + archetypes[archetype].emplace(e); +} template inline void world::set(entity_id e) @@ -300,34 +282,30 @@ namespace zecsy template inline void world::remove(entity_id e) { - auto id = get_component_id(); - auto& comp_set = entity_to_comps[e]; - - if(comp_set.erase(id) > 0) + if(!has(e)) { - std::vector old_key(comp_set.begin(), comp_set.end()); - old_key.push_back(id); - std::sort(old_key.begin(), old_key.end()); - - auto& old_group = archetypes[old_key]; - old_group.erase(e); - - std::vector new_key(comp_set.begin(), comp_set.end()); - std::sort(new_key.begin(), new_key.end()); - - archetypes[new_key].emplace(e); - - if(archetypes[old_key].empty()) - { - archetypes.erase(old_key); - } - - auto& pool = pools[id]; - auto index = pool.entity_to_index[e]; - pool.free_list.push_back(index); - pool.entity_to_index.erase(e); - pool.index_to_entity.erase(index); + return; } + + auto id = get_component_id(); + auto& archetype = entity_to_comps[e]; + + auto& old_group = archetypes[archetype]; + old_group.erase(e); + + if(old_group.empty()) + { + archetypes.erase(archetype); + } + + archetype.reset(id); + archetypes[archetype].emplace(e); + + auto& pool = pools[id]; + auto index = pool.entity_to_index[e]; + pool.free_list.push_back(index); + pool.entity_to_index.erase(e); + pool.index_to_entity.erase(index); } template @@ -364,8 +342,11 @@ namespace zecsy template inline void world::query(std::invocable auto&& system) { - std::vector required = {get_component_id()...}; - std::sort(required.begin(), required.end()); + /*std::vector required = {get_component_id()...};*/ + + archetype_signature required; + + (required.set(get_component_id()), ...); query_archetypes_checked = 0; query_entities_processed = 0; @@ -374,18 +355,7 @@ namespace zecsy { query_archetypes_checked++; - bool match = true; - for(comp_id req_id: required) - { - if(!std::binary_search(archetype_key.begin(), - archetype_key.end(), req_id)) - { - match = false; - break; - } - } - - if(match) + if((archetype_key & required) == required) { query_entities_processed += entities.size(); for(entity_id e: entities)