#pragma once #include #include #include #include #include #include #include #include #include #include namespace zecsy { using entity_id = uint64_t; template concept Component = [] { static_assert((std::is_default_constructible_v && ...), "Should have a default constructor"); static_assert((std::is_trivially_copyable_v && ...), "Should be trivially copyable"); static_assert((std::is_trivially_destructible_v && ...), "Should be trivially destructible"); static_assert((std::is_standard_layout_v && ...), "Should have standard layout"); return true; }(); class world final { public: entity_id make_entity(); void destroy_entity(entity_id e); bool is_alive(entity_id e) const; size_t components_in_entity(entity_id e) const; size_t entity_count() const; size_t total_component_count() const; size_t archetype_count() const; template size_t component_count(); template bool has(entity_id e) const; template bool has(entity_id e) const; template T& get(entity_id e); template void set(entity_id e); template void set(entity_id e, const T& comp); template void set(entity_id e); template void set(entity_id e, const First& comp0, const Second& comp1, const Rest&... rest_comps); template void remove(entity_id e); template void remove(entity_id e); template void query(std::invocable auto&& system); size_t get_archetypes_checked() const; size_t get_entities_processed() const; private: using comp_id = size_t; std::set alive_entities; std::unordered_map> entity_to_comps; entity_id entity_counter = 0; size_t query_archetypes_checked = 0; size_t query_entities_processed = 0; struct component_pool { std::vector data; std::vector free_list; std::unordered_map entity_to_index; std::unordered_map index_to_entity; }; std::unordered_map pools; using archetype_signature = std::vector; using entity_group = std::set; struct archetype_hash { size_t operator()(const archetype_signature& vec) const; }; std::unordered_map archetypes; template static comp_id get_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 inline world::comp_id world::get_component_id() { static comp_id id = next_component_id++; return id; } inline world::comp_id world::next_component_id = 0; inline size_t world::components_in_entity(entity_id e) const { return entity_to_comps.contains(e) ? entity_to_comps.at(e).size() : 0; } inline size_t world::entity_count() const { return alive_entities.size(); } inline size_t world::total_component_count() const { size_t count = 0; for(const auto& [id, pool]: pools) { count += pool.entity_to_index.size(); } return count; } inline size_t world::archetype_count() const { return archetypes.size(); } template inline size_t world::component_count() { const comp_id id = get_component_id(); const auto it = pools.find(id); return it != pools.end() ? it->second.entity_to_index.size() : 0; } inline size_t world::get_archetypes_checked() const { return query_archetypes_checked; } inline size_t world::get_entities_processed() const { return query_entities_processed; } inline entity_id world::make_entity() { auto id = ++entity_counter; alive_entities.emplace(id); entity_to_comps[id] = {}; std::vector key; auto& group = archetypes[key]; group.emplace(id); return id; } inline void world::destroy_entity(entity_id e) { alive_entities.erase(e); auto& comp_set = entity_to_comps[e]; std::vector key(comp_set.begin(), comp_set.end()); auto& group = archetypes[key]; group.erase(e); if(archetypes[key].empty()) { archetypes.erase(key); } for(comp_id id: comp_set) { 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); } inline bool world::is_alive(entity_id e) const { return alive_entities.contains(e); } template inline bool world::has(entity_id e) const { if(entity_to_comps.contains(e)) { return entity_to_comps.at(e).contains(get_component_id()); } return false; } template inline T& world::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 world::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.back(); pool.free_list.pop_back(); } 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; auto& comp_set = entity_to_comps[e]; std::set old_set = comp_set; auto [it, inserted] = comp_set.insert(id); if(inserted) { 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); } } template inline void world::set(entity_id e) { set(e, T{}); } 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) { 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); } } template inline bool world::has(entity_id e) const { return has(e) && has(e) && (has(e) && ...); } template inline void world::set(entity_id e) { set(e, First{}); set(e, Second{}); (set(e, Rest{}), ...); } template inline void world::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 world::remove(entity_id e) { remove(e); remove(e); (remove(e), ...); } template inline void world::query(std::invocable auto&& system) { std::vector required = {get_component_id()...}; std::sort(required.begin(), required.end()); query_archetypes_checked = 0; query_entities_processed = 0; for(const auto& [archetype_key, entities]: archetypes) { 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) { query_entities_processed += entities.size(); for(entity_id e: entities) { system(e, get(e)...); } } } } }; // namespace zecsy