Archetypes now based on bitsets (performance++)
This commit is contained in:
		
							parent
							
								
									219af9d803
								
							
						
					
					
						commit
						bda170bf6d
					
				
					 2 changed files with 77 additions and 107 deletions
				
			
		|  | @ -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
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								zecsy.hpp
									
									
									
									
									
								
							|  | @ -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) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue