Archetypes now based on bitsets (performance++)

This commit is contained in:
NukeBird 2025-02-20 17:49:49 +03:00
parent 219af9d803
commit bda170bf6d
2 changed files with 77 additions and 107 deletions

View file

@ -604,8 +604,8 @@ namespace
// Benchmark entity counts // Benchmark entity counts
constexpr int SMALL = 1'000; constexpr int SMALL = 1'000;
constexpr int MEDIUM = 5'000; constexpr int MEDIUM = 10'000;
constexpr int LARGE = 10'000; constexpr int LARGE = 50'000;
} // namespace } // namespace
TEST_CASE("Core operations benchmarks", "[benchmark]") 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) Catch::Benchmark::Chronometer meter)
{ {
meter.measure( 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) Catch::Benchmark::Chronometer meter)
{ {
meter.measure( 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) Catch::Benchmark::Chronometer meter)
{ {
meter.measure( 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) Catch::Benchmark::Chronometer meter)
{ {
meter.measure( 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) Catch::Benchmark::Chronometer meter)
{ {
world w; 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) Catch::Benchmark::Chronometer meter)
{ {
world w; 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) Catch::Benchmark::Chronometer meter)
{ {
world w; 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) Catch::Benchmark::Chronometer meter)
{ {
world w; world w;

164
zecsy.hpp
View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <algorithm> #include <algorithm>
#include <bitset>
#include <concepts> #include <concepts>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
@ -10,6 +11,10 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#ifndef ZECSY_MAX_COMPONENTS
#define ZECSY_MAX_COMPONENTS 32
#endif // !ZECSY_MAX_COMPONENTS
namespace zecsy namespace zecsy
{ {
using entity_id = uint64_t; using entity_id = uint64_t;
@ -79,8 +84,9 @@ namespace zecsy
private: private:
using comp_id = size_t; using comp_id = size_t;
std::set<entity_id> alive_entities; using zecsy_bits = std::bitset<ZECSY_MAX_COMPONENTS>;
std::unordered_map<entity_id, std::set<comp_id>> entity_to_comps;
std::unordered_map<entity_id, zecsy_bits> entity_to_comps;
entity_id entity_counter = 0; entity_id entity_counter = 0;
size_t query_archetypes_checked = 0; size_t query_archetypes_checked = 0;
@ -96,16 +102,10 @@ namespace zecsy
std::unordered_map<comp_id, component_pool> pools; std::unordered_map<comp_id, component_pool> pools;
using archetype_signature = std::vector<comp_id>; using archetype_signature = zecsy_bits;
using entity_group = std::set<entity_id>; using entity_group = std::set<entity_id>;
struct archetype_hash std::unordered_map<archetype_signature, entity_group> archetypes;
{
size_t operator()(const archetype_signature& vec) const;
};
std::unordered_map<archetype_signature, entity_group, archetype_hash>
archetypes;
template<Component T> template<Component T>
static comp_id get_component_id(); static comp_id get_component_id();
@ -113,17 +113,6 @@ namespace zecsy
static comp_id next_component_id; 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<Component T> template<Component T>
inline world::comp_id world::get_component_id() 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 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 inline size_t world::entity_count() const
{ {
return alive_entities.size(); return entity_to_comps.size();
} }
inline size_t world::total_component_count() const inline size_t world::total_component_count() const
@ -181,10 +170,9 @@ namespace zecsy
inline entity_id world::make_entity() inline entity_id world::make_entity()
{ {
auto id = ++entity_counter; auto id = ++entity_counter;
alive_entities.emplace(id);
entity_to_comps[id] = {}; entity_to_comps[id] = {};
std::vector<comp_id> key; archetype_signature key;
auto& group = archetypes[key]; auto& group = archetypes[key];
group.emplace(id); group.emplace(id);
@ -193,26 +181,26 @@ namespace zecsy
inline void world::destroy_entity(entity_id e) 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]; auto& group = archetypes[archetype];
std::vector<comp_id> key(comp_set.begin(), comp_set.end());
auto& group = archetypes[key];
group.erase(e); 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]; if(archetype.test(id))
auto index = pool.entity_to_index[e]; {
pool.entity_to_index.erase(e); auto& pool = pools[id];
pool.index_to_entity.erase(index); auto index = pool.entity_to_index[e];
pool.free_list.emplace_back(index); pool.entity_to_index.erase(e);
pool.index_to_entity.erase(index);
pool.free_list.emplace_back(index);
}
} }
entity_to_comps.erase(e); entity_to_comps.erase(e);
@ -220,7 +208,7 @@ namespace zecsy
inline bool world::is_alive(entity_id e) const inline bool world::is_alive(entity_id e) const
{ {
return alive_entities.contains(e); return entity_to_comps.contains(e);
} }
template<Component T> template<Component T>
@ -228,7 +216,7 @@ namespace zecsy
{ {
if(entity_to_comps.contains(e)) if(entity_to_comps.contains(e))
{ {
return entity_to_comps.at(e).contains(get_component_id<T>()); return entity_to_comps.at(e).test(get_component_id<T>());
} }
return false; return false;
} }
@ -270,26 +258,20 @@ namespace zecsy
pool.entity_to_index[e] = index; pool.entity_to_index[e] = index;
pool.index_to_entity[index] = e; pool.index_to_entity[index] = e;
auto& comp_set = entity_to_comps[e]; auto& archetype = entity_to_comps[e];
std::set<comp_id> old_set = comp_set; auto old_archetype = archetype;
auto [it, inserted] = comp_set.insert(id); archetype.set(id);
if(inserted) auto& group = archetypes[old_archetype];
group.erase(e);
if(archetypes[old_archetype].empty())
{ {
std::vector<comp_id> old_key(old_set.begin(), old_set.end()); archetypes.erase(old_archetype);
auto& group = archetypes[old_key];
group.erase(e);
if(archetypes[old_key].empty())
{
archetypes.erase(old_key);
}
std::vector<comp_id> new_key(comp_set.begin(), comp_set.end());
archetypes[new_key].emplace(e);
} }
}
archetypes[archetype].emplace(e);
}
template<Component T> template<Component T>
inline void world::set(entity_id e) inline void world::set(entity_id e)
@ -300,34 +282,30 @@ namespace zecsy
template<Component T> template<Component T>
inline void world::remove(entity_id e) inline void world::remove(entity_id e)
{ {
auto id = get_component_id<T>(); if(!has<T>(e))
auto& comp_set = entity_to_comps[e];
if(comp_set.erase(id) > 0)
{ {
std::vector<comp_id> old_key(comp_set.begin(), comp_set.end()); return;
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<comp_id> 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);
} }
auto id = get_component_id<T>();
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<Component First, Component Second, Component... Rest> template<Component First, Component Second, Component... Rest>
@ -364,8 +342,11 @@ namespace zecsy
template<Component... T> template<Component... T>
inline void world::query(std::invocable<entity_id, T&...> auto&& system) inline void world::query(std::invocable<entity_id, T&...> auto&& system)
{ {
std::vector<comp_id> required = {get_component_id<T>()...}; /*std::vector<comp_id> required = {get_component_id<T>()...};*/
std::sort(required.begin(), required.end());
archetype_signature required;
(required.set(get_component_id<T>()), ...);
query_archetypes_checked = 0; query_archetypes_checked = 0;
query_entities_processed = 0; query_entities_processed = 0;
@ -374,18 +355,7 @@ namespace zecsy
{ {
query_archetypes_checked++; query_archetypes_checked++;
bool match = true; if((archetype_key & required) == required)
for(comp_id req_id: required)
{
if(!std::binary_search(archetype_key.begin(),
archetype_key.end(), req_id))
{
match = false;
break;
}
}
if(match)
{ {
query_entities_processed += entities.size(); query_entities_processed += entities.size();
for(entity_id e: entities) for(entity_id e: entities)