diff --git a/libs/ecs/include/psemek/ecs/detail/component_hash.hpp b/libs/ecs/include/psemek/ecs/detail/component_hash.hpp new file mode 100644 index 00000000..53c5a9f3 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/component_hash.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +namespace psemek::ecs::detail +{ + + inline std::size_t component_hash(util::span const & uuids) + { + std::size_t result = 0xcd5694d2b3f3443eull; + for (auto const & uuid : uuids) + result ^= std::hash{}(uuid); + return result; + } + +} diff --git a/libs/ecs/include/psemek/ecs/detail/component_index.hpp b/libs/ecs/include/psemek/ecs/detail/component_index.hpp deleted file mode 100644 index 8219cfad..00000000 --- a/libs/ecs/include/psemek/ecs/detail/component_index.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace psemek::ecs::detail -{ - - template - struct component_uuid_holder - { - util::uuid uuids[sizeof...(Components)] { Components::uuid() ... }; - - auto get() const - { - return util::span(uuids); - } - }; - - template <> - struct component_uuid_holder<> - { - auto get() const - { - return util::span(); - } - }; - - struct component_index - { - template - component_mask make_component_mask(UUIDS const & ... uuids) - { - component_mask result; - (result.set(storage_.insert(uuids), true), ...); - return result; - } - - component_mask make_component_mask(util::span uuids) - { - component_mask result; - for (util::uuid const & uuid : uuids) - result.set(storage_.insert(uuid), true); - return result; - } - - private: - util::unique_sequential_storage storage_; - }; - -} diff --git a/libs/ecs/include/psemek/ecs/detail/component_mask.hpp b/libs/ecs/include/psemek/ecs/detail/component_mask.hpp deleted file mode 100644 index 8a7dda46..00000000 --- a/libs/ecs/include/psemek/ecs/detail/component_mask.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include - -namespace psemek::ecs::detail -{ - - using component_mask = util::dynamic_bitset; - -} diff --git a/libs/ecs/include/psemek/ecs/detail/component_uuid_helper.hpp b/libs/ecs/include/psemek/ecs/detail/component_uuid_helper.hpp new file mode 100644 index 00000000..447f6a2c --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/component_uuid_helper.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +namespace psemek::ecs::detail +{ + + template + struct component_uuid_helper + { + util::uuid uuids[sizeof...(Components)] { Components::uuid() ... }; + + auto get() const + { + return util::span(uuids); + } + }; + + template <> + struct component_uuid_helper<> + { + auto get() const + { + return util::span(); + } + }; + +} diff --git a/libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp b/libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp index cd54e3ec..63609898 100644 --- a/libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp +++ b/libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp @@ -1,50 +1,104 @@ #pragma once #include -#include +#include +#include #include namespace psemek::ecs::detail { + struct query_cache_set + { + std::size_t hash; + util::hash_set uuids; + std::vector> caches; + }; + + struct query_cache_hash + { + std::size_t operator()(util::span const & uuids) const + { + return component_hash(uuids); + } + + std::size_t operator()(std::unique_ptr const & set) const + { + return set->hash; + } + }; + + struct query_cache_equal + { + bool operator()(util::span const & uuids, std::unique_ptr const & set) const + { + if (uuids.size() != set->uuids.size()) + return false; + + for (auto const & uuid : uuids) + if (!set->uuids.contains(uuid)) + return false; + + return true; + } + + bool operator()(std::unique_ptr const & set1, std::unique_ptr const & set2) const + { + return set1.get() == set2.get(); + } + + }; + // TODO: store query caches in a bitmask trie balanced by subtree size struct query_cache_container { - std::shared_ptr create(component_mask const & mask, util::span component_uuids) + std::shared_ptr create(util::span component_uuids) { auto result = std::make_shared(); result->component_uuids.assign(component_uuids.begin(), component_uuids.end()); - get(mask).emplace_back(result); + auto it = caches_.find(component_uuids); + if (it == caches_.end()) + { + auto value = std::make_unique(); + for (auto const & uuid : component_uuids) + value->uuids.insert(uuid); + value->hash = component_hash(component_uuids); + it = caches_.insert(std::move(value)).first; + } + it->get()->caches.push_back(result); return result; } - template - void apply(Function && function, component_mask const & mask) + template + void apply(Function && function, ContainsUUID && contains_uuid) { - for (auto & caches : caches_) + for (auto & cache_set : caches_) { - if (!util::is_subset(caches.first, mask)) continue; - filter(caches.second); - for (auto & cache : caches.second) + bool good = true; + for (auto const & uuid : cache_set->uuids) + if (!contains_uuid(uuid)) + { + good = false; + break; + } + + if (!good) + continue; + + filter(cache_set->caches); + for (auto & cache : cache_set->caches) function(*cache.lock()); } } private: - std::unordered_map>> caches_; + util::hash_set, query_cache_hash, query_cache_equal> caches_; static void filter(std::vector> & caches) { caches.erase(std::remove_if(caches.begin(), caches.end(), [](std::weak_ptr const & weak){ return !weak.lock(); }), caches.end()); } - - std::vector> & get(component_mask const & mask) - { - auto & result = caches_[mask]; - filter(result); - return result; - } }; } diff --git a/libs/ecs/include/psemek/ecs/detail/table.hpp b/libs/ecs/include/psemek/ecs/detail/table.hpp index 2a350fd4..8cfd14bf 100644 --- a/libs/ecs/include/psemek/ecs/detail/table.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -28,31 +29,31 @@ namespace psemek::ecs::detail std::size_t current_row = 0; }; - util::span get_component_uuids() const + std::size_t hash() const { - return component_uuids_; + return hash_; } - std::optional get_component_column(util::uuid const & uuid) const + std::optional component_column(util::uuid const & uuid) const { if (auto it = component_uuid_to_column_.find(uuid); it != component_uuid_to_column_.end()) return it->second; return std::nullopt; } - util::span get_component_pointers() const + util::span component_pointers() const { return component_pointers_; } - util::span get_entity_handles() const + util::span entity_handles() const { return entity_handles_; } std::size_t component_count() const { - return component_uuids_.size(); + return component_pointers_.size(); } std::size_t row_count() const @@ -93,6 +94,7 @@ namespace psemek::ecs::detail virtual ~table() = default; protected: + std::size_t hash_; std::vector component_uuids_; util::hash_map component_uuid_to_column_; std::vector component_pointers_; @@ -137,10 +139,11 @@ namespace psemek::ecs::detail table_impl::table_impl(util::span component_uuids) { assert(sizeof...(Components) == component_uuids.size()); + hash_ = component_hash(component_uuids); component_uuids_.assign(component_uuids.begin(), component_uuids.end()); component_pointers_.resize(sizeof...(Components)); - for (std::size_t i = 0; i < component_uuids_.size(); ++i) - component_uuid_to_column_.insert({component_uuids_[i], i}); + for (std::size_t i = 0; i < component_uuids.size(); ++i) + component_uuid_to_column_.insert({component_uuids[i], i}); } template diff --git a/libs/ecs/include/psemek/ecs/detail/table_container.hpp b/libs/ecs/include/psemek/ecs/detail/table_container.hpp index a103516a..78bf05ce 100644 --- a/libs/ecs/include/psemek/ecs/detail/table_container.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table_container.hpp @@ -1,46 +1,85 @@ #pragma once -#include +#include #include +#include #include namespace psemek::ecs::detail { + struct table_hashset_hash + { + std::size_t operator()(util::span const & uuids) const + { + return component_hash(uuids); + } + + std::size_t operator()(std::unique_ptr const & table) const + { + return table->hash(); + } + }; + + struct table_hashset_equal + { + bool operator()(util::span const & uuids, std::unique_ptr
const & table) const + { + if (uuids.size() != table->component_count()) + return false; + for (auto const & uuid : uuids) + if (!table->component_column(uuid)) + return false; + return true; + } + + bool operator()(std::unique_ptr
const & table1, std::unique_ptr
const & table2) const + { + return table1.get() == table2.get(); + } + }; + // TODO: store tables in a bitmask trie balanced by subtree size struct table_container { template - std::pair
insert(component_mask const & mask, util::span component_uuids); + std::pair
insert(util::span component_uuids); template - void apply(Function && function, component_mask const & mask); + void apply(Function && function, util::span component_uuids); private: - std::unordered_map> tables_; + util::hash_set, table_hashset_hash, table_hashset_equal> tables_; }; template - std::pair
table_container::insert(component_mask const & mask, util::span component_uuids) + std::pair
table_container::insert(util::span component_uuids) { - auto & result = tables_[mask]; - bool created = false; - if (!result) - { - result = std::make_unique>(component_uuids); - created = true; - } - return {result.get(), created}; + auto it = tables_.find(component_uuids); + if (it != tables_.end()) + return {it->get(), false}; + + auto table = std::make_unique>(component_uuids); + auto result = table.get(); + tables_.insert(std::move(table)); + return {result, true}; } template - void table_container::apply(Function && function, component_mask const & mask) + void table_container::apply(Function && function, util::span component_uuids) { for (auto & table : tables_) { - if (!util::is_subset(mask, table.first)) continue; - function(*table.second); + bool good = true; + for (auto const & uuid : component_uuids) + if (!table->component_column(uuid)) + { + good = false; + break; + } + + if (good) function(*table); } } diff --git a/libs/ecs/include/psemek/ecs/entity_container.hpp b/libs/ecs/include/psemek/ecs/entity_container.hpp index 012446ef..32dcea16 100644 --- a/libs/ecs/include/psemek/ecs/entity_container.hpp +++ b/libs/ecs/include/psemek/ecs/entity_container.hpp @@ -1,11 +1,11 @@ #pragma once -#include #include #include #include #include #include +#include #include #include #include @@ -164,7 +164,7 @@ namespace psemek::ecs * types, the behavior is undefined. * If the function accesses passed components after destroying the * currently visited entity, the behavior is undefined. - * If the function recursively calls `apply()`, the behavior is undefined. + * If the function recursively calls `apply()` or `batch_apply()`, the behavior is undefined. */ template void apply(Function && function, query_cache cache = {}); @@ -182,13 +182,13 @@ namespace psemek::ecs * If the query cache wasn't created with the exact sequence of component * types, the behavior is undefined. * If the function creates or destroyes entities, the behavior is undefined. + * If the function recursively calls `apply()` or `batch_apply()`, the behavior is undefined. */ template void batch_apply(Function && function, query_cache cache = {}); private: detail::entity_list entity_list_; - mutable detail::component_index component_index_; detail::table_container table_container_; detail::query_cache_container query_cache_container_; @@ -200,10 +200,9 @@ namespace psemek::ecs { static_assert(detail::all_different_types_v, "all component types must be different"); - detail::component_uuid_holder uuids; - detail::component_mask mask = component_index_.make_component_mask(uuids.get()); + detail::component_uuid_helper uuids; - auto insert_result = table_container_.insert(mask, uuids.get()); + auto insert_result = table_container_.insert(uuids.get()); auto table = insert_result.first; bool created = insert_result.second; @@ -211,7 +210,7 @@ namespace psemek::ecs { query_cache_container_.apply([table](detail::query_cache & cache){ cache.add(table); - }, mask); + }, [table](util::uuid const & uuid){ return table->component_column(uuid) != std::nullopt; }); } if (table->get_iteration_data()) @@ -230,14 +229,13 @@ namespace psemek::ecs template query_cache entity_container::cache() { - detail::component_uuid_holder uuids; - detail::component_mask mask = component_index_.make_component_mask(uuids.get()); + detail::component_uuid_helper uuids; - auto result = query_cache_container_.create(mask, uuids.get()); + auto result = query_cache_container_.create(uuids.get()); table_container_.apply([&](detail::table & table){ result->add(&table); - }, mask); + }, uuids.get()); return result; } @@ -252,10 +250,10 @@ namespace psemek::ecs { auto & iteration_data = entry.table->get_iteration_data(); iteration_data.emplace(); - detail::static_apply_helper apply_helper(*this, entry.table->get_entity_handles()); + detail::static_apply_helper apply_helper(*this, entry.table->entity_handles()); for (std::size_t i = 0; i < sizeof...(Components); ++i) - apply_helper.pointers[i] = entry.table->get_component_pointers()[entry.column_ids[i]]; + apply_helper.pointers[i] = entry.table->component_pointers()[entry.column_ids[i]]; for (std::size_t i = 0; i < entry.table->row_count(); ++i) { @@ -290,10 +288,10 @@ namespace psemek::ecs for (auto const & entry : cache->tables) { - detail::static_apply_helper apply_helper(*this, entry.table->get_entity_handles()); + detail::static_apply_helper apply_helper(*this, entry.table->entity_handles()); for (std::size_t i = 0; i < sizeof...(Components); ++i) - apply_helper.pointers[i] = entry.table->get_component_pointers()[entry.column_ids[i]]; + apply_helper.pointers[i] = entry.table->component_pointers()[entry.column_ids[i]]; apply_helper.batch_apply(function); } @@ -309,11 +307,11 @@ namespace psemek::ecs { util::uuid const uuid = Component::uuid(); - auto column = table_->get_component_column(uuid); + auto column = table_->component_column(uuid); if (!column) return nullptr; - return reinterpret_cast(table_->get_component_pointers()[*column].data + detail::stride() * row_); + return reinterpret_cast(table_->component_pointers()[*column].data + detail::stride() * row_); } template @@ -321,7 +319,7 @@ namespace psemek::ecs { if (auto ptr = get_if()) return *ptr; - throw component_not_found_exception(typeid(Component), table_->get_entity_handles()[row_]); + throw component_not_found_exception(typeid(Component), table_->entity_handles()[row_]); } template diff --git a/libs/ecs/source/detail/query_cache.cpp b/libs/ecs/source/detail/query_cache.cpp index b30c7ab4..5c472208 100644 --- a/libs/ecs/source/detail/query_cache.cpp +++ b/libs/ecs/source/detail/query_cache.cpp @@ -10,7 +10,7 @@ namespace psemek::ecs::detail entry.table = table; for (auto const & uuid : component_uuids) - entry.column_ids.push_back(*(table->get_component_column(uuid))); + entry.column_ids.push_back(*(table->component_column(uuid))); } } diff --git a/libs/ecs/source/entity_container.cpp b/libs/ecs/source/entity_container.cpp index 875d2576..e9bdaff0 100644 --- a/libs/ecs/source/entity_container.cpp +++ b/libs/ecs/source/entity_container.cpp @@ -35,7 +35,7 @@ namespace psemek::ecs void entity_container::remove_row(detail::table & table, std::uint32_t row, util::span entities) { // Swap with the last row in that table - auto table_entity_handles = table.get_entity_handles(); + auto table_entity_handles = table.entity_handles(); table.swap_rows(row, table_entity_handles.size() - 1); table.pop_row(); auto swap_handle = table_entity_handles[row];