Remove component bitsets from ecs; use uuid hash tables instead
This commit is contained in:
parent
809a0ec212
commit
31ffd4dc54
10 changed files with 202 additions and 124 deletions
18
libs/ecs/include/psemek/ecs/detail/component_hash.hpp
Normal file
18
libs/ecs/include/psemek/ecs/detail/component_hash.hpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/util/uuid.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
#include <psemek/util/hash.hpp>
|
||||
|
||||
namespace psemek::ecs::detail
|
||||
{
|
||||
|
||||
inline std::size_t component_hash(util::span<util::uuid const> const & uuids)
|
||||
{
|
||||
std::size_t result = 0xcd5694d2b3f3443eull;
|
||||
for (auto const & uuid : uuids)
|
||||
result ^= std::hash<util::uuid>{}(uuid);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/ecs/detail/component_mask.hpp>
|
||||
#include <psemek/util/uuid.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
#include <psemek/util/unique_sequential_storage.hpp>
|
||||
|
||||
namespace psemek::ecs::detail
|
||||
{
|
||||
|
||||
template <typename ... Components>
|
||||
struct component_uuid_holder
|
||||
{
|
||||
util::uuid uuids[sizeof...(Components)] { Components::uuid() ... };
|
||||
|
||||
auto get() const
|
||||
{
|
||||
return util::span<util::uuid const>(uuids);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct component_uuid_holder<>
|
||||
{
|
||||
auto get() const
|
||||
{
|
||||
return util::span<util::uuid const>();
|
||||
}
|
||||
};
|
||||
|
||||
struct component_index
|
||||
{
|
||||
template <typename ... UUIDS>
|
||||
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<util::uuid const> uuids)
|
||||
{
|
||||
component_mask result;
|
||||
for (util::uuid const & uuid : uuids)
|
||||
result.set(storage_.insert(uuid), true);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
util::unique_sequential_storage<util::uuid> storage_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/util/dynamic_bitset.hpp>
|
||||
|
||||
namespace psemek::ecs::detail
|
||||
{
|
||||
|
||||
using component_mask = util::dynamic_bitset;
|
||||
|
||||
}
|
||||
29
libs/ecs/include/psemek/ecs/detail/component_uuid_helper.hpp
Normal file
29
libs/ecs/include/psemek/ecs/detail/component_uuid_helper.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/util/uuid.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
|
||||
namespace psemek::ecs::detail
|
||||
{
|
||||
|
||||
template <typename ... Components>
|
||||
struct component_uuid_helper
|
||||
{
|
||||
util::uuid uuids[sizeof...(Components)] { Components::uuid() ... };
|
||||
|
||||
auto get() const
|
||||
{
|
||||
return util::span<util::uuid const>(uuids);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct component_uuid_helper<>
|
||||
{
|
||||
auto get() const
|
||||
{
|
||||
return util::span<util::uuid const>();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,50 +1,104 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/ecs/detail/query_cache.hpp>
|
||||
#include <psemek/ecs/detail/component_mask.hpp>
|
||||
#include <psemek/ecs/detail/component_hash.hpp>
|
||||
#include <psemek/util/hash_table.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::ecs::detail
|
||||
{
|
||||
|
||||
struct query_cache_set
|
||||
{
|
||||
std::size_t hash;
|
||||
util::hash_set<util::uuid> uuids;
|
||||
std::vector<std::weak_ptr<query_cache>> caches;
|
||||
};
|
||||
|
||||
struct query_cache_hash
|
||||
{
|
||||
std::size_t operator()(util::span<util::uuid const> const & uuids) const
|
||||
{
|
||||
return component_hash(uuids);
|
||||
}
|
||||
|
||||
std::size_t operator()(std::unique_ptr<query_cache_set> const & set) const
|
||||
{
|
||||
return set->hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct query_cache_equal
|
||||
{
|
||||
bool operator()(util::span<util::uuid const> const & uuids, std::unique_ptr<query_cache_set> 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<query_cache_set> const & set1, std::unique_ptr<query_cache_set> 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<query_cache> create(component_mask const & mask, util::span<util::uuid const> component_uuids)
|
||||
std::shared_ptr<query_cache> create(util::span<util::uuid const> component_uuids)
|
||||
{
|
||||
auto result = std::make_shared<query_cache>();
|
||||
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<query_cache_set>();
|
||||
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 <typename Function>
|
||||
void apply(Function && function, component_mask const & mask)
|
||||
template <typename Function, typename ContainsUUID>
|
||||
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<component_mask, std::vector<std::weak_ptr<query_cache>>> caches_;
|
||||
util::hash_set<std::unique_ptr<query_cache_set>, query_cache_hash, query_cache_equal> caches_;
|
||||
|
||||
static void filter(std::vector<std::weak_ptr<query_cache>> & caches)
|
||||
{
|
||||
caches.erase(std::remove_if(caches.begin(), caches.end(), [](std::weak_ptr<query_cache> const & weak){ return !weak.lock(); }), caches.end());
|
||||
}
|
||||
|
||||
std::vector<std::weak_ptr<query_cache>> & get(component_mask const & mask)
|
||||
{
|
||||
auto & result = caches_[mask];
|
||||
filter(result);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <psemek/ecs/entity_handle.hpp>
|
||||
#include <psemek/ecs/detail/stride.hpp>
|
||||
#include <psemek/ecs/detail/component_hash.hpp>
|
||||
#include <psemek/util/uuid.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
#include <psemek/util/hash_table.hpp>
|
||||
|
|
@ -28,31 +29,31 @@ namespace psemek::ecs::detail
|
|||
std::size_t current_row = 0;
|
||||
};
|
||||
|
||||
util::span<util::uuid const> get_component_uuids() const
|
||||
std::size_t hash() const
|
||||
{
|
||||
return component_uuids_;
|
||||
return hash_;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> get_component_column(util::uuid const & uuid) const
|
||||
std::optional<std::size_t> 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<component_pointer const> get_component_pointers() const
|
||||
util::span<component_pointer const> component_pointers() const
|
||||
{
|
||||
return component_pointers_;
|
||||
}
|
||||
|
||||
util::span<entity_handle const> get_entity_handles() const
|
||||
util::span<entity_handle const> 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<util::uuid> component_uuids_;
|
||||
util::hash_map<util::uuid, std::size_t> component_uuid_to_column_;
|
||||
std::vector<component_pointer> component_pointers_;
|
||||
|
|
@ -137,10 +139,11 @@ namespace psemek::ecs::detail
|
|||
table_impl<Components...>::table_impl(util::span<util::uuid const> 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 <typename ... Components>
|
||||
|
|
|
|||
|
|
@ -1,46 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/ecs/detail/component_mask.hpp>
|
||||
#include <psemek/ecs/detail/component_hash.hpp>
|
||||
#include <psemek/ecs/detail/table.hpp>
|
||||
#include <psemek/util/hash_table.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace psemek::ecs::detail
|
||||
{
|
||||
|
||||
struct table_hashset_hash
|
||||
{
|
||||
std::size_t operator()(util::span<util::uuid const> const & uuids) const
|
||||
{
|
||||
return component_hash(uuids);
|
||||
}
|
||||
|
||||
std::size_t operator()(std::unique_ptr<table> const & table) const
|
||||
{
|
||||
return table->hash();
|
||||
}
|
||||
};
|
||||
|
||||
struct table_hashset_equal
|
||||
{
|
||||
bool operator()(util::span<util::uuid const> const & uuids, std::unique_ptr<table> 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<table> const & table1, std::unique_ptr<table> const & table2) const
|
||||
{
|
||||
return table1.get() == table2.get();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: store tables in a bitmask trie balanced by subtree size
|
||||
struct table_container
|
||||
{
|
||||
template <typename ... Components>
|
||||
std::pair<table *, bool> insert(component_mask const & mask, util::span<util::uuid const> component_uuids);
|
||||
std::pair<table *, bool> insert(util::span<util::uuid const> component_uuids);
|
||||
|
||||
template <typename Function>
|
||||
void apply(Function && function, component_mask const & mask);
|
||||
void apply(Function && function, util::span<util::uuid const> component_uuids);
|
||||
|
||||
private:
|
||||
std::unordered_map<component_mask, std::unique_ptr<table>> tables_;
|
||||
util::hash_set<std::unique_ptr<table>, table_hashset_hash, table_hashset_equal> tables_;
|
||||
};
|
||||
|
||||
template <typename ... Components>
|
||||
std::pair<table *, bool> table_container::insert(component_mask const & mask, util::span<util::uuid const> component_uuids)
|
||||
std::pair<table *, bool> table_container::insert(util::span<util::uuid const> component_uuids)
|
||||
{
|
||||
auto & result = tables_[mask];
|
||||
bool created = false;
|
||||
if (!result)
|
||||
{
|
||||
result = std::make_unique<table_impl<Components...>>(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<table_impl<Components...>>(component_uuids);
|
||||
auto result = table.get();
|
||||
tables_.insert(std::move(table));
|
||||
return {result, true};
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void table_container::apply(Function && function, component_mask const & mask)
|
||||
void table_container::apply(Function && function, util::span<util::uuid const> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/ecs/detail/component_index.hpp>
|
||||
#include <psemek/ecs/detail/entity_list.hpp>
|
||||
#include <psemek/ecs/detail/table_container.hpp>
|
||||
#include <psemek/ecs/detail/query_cache_container.hpp>
|
||||
#include <psemek/ecs/detail/apply_helper.hpp>
|
||||
#include <psemek/ecs/detail/all_different_types.hpp>
|
||||
#include <psemek/ecs/detail/component_uuid_helper.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
#include <psemek/util/range.hpp>
|
||||
#include <psemek/util/exception.hpp>
|
||||
|
|
@ -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 <typename ... Components, typename Function>
|
||||
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 <typename ... Components, typename Function>
|
||||
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<Components...>, "all component types must be different");
|
||||
|
||||
detail::component_uuid_holder<Components...> uuids;
|
||||
detail::component_mask mask = component_index_.make_component_mask(uuids.get());
|
||||
detail::component_uuid_helper<Components...> uuids;
|
||||
|
||||
auto insert_result = table_container_.insert<Components...>(mask, uuids.get());
|
||||
auto insert_result = table_container_.insert<Components...>(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 <typename ... Components>
|
||||
query_cache entity_container::cache()
|
||||
{
|
||||
detail::component_uuid_holder<Components...> uuids;
|
||||
detail::component_mask mask = component_index_.make_component_mask(uuids.get());
|
||||
detail::component_uuid_helper<Components...> 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<Components...> apply_helper(*this, entry.table->get_entity_handles());
|
||||
detail::static_apply_helper<Components...> 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<Components...> apply_helper(*this, entry.table->get_entity_handles());
|
||||
detail::static_apply_helper<Components...> 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<Component *>(table_->get_component_pointers()[*column].data + detail::stride<Component>() * row_);
|
||||
return reinterpret_cast<Component *>(table_->component_pointers()[*column].data + detail::stride<Component>() * row_);
|
||||
}
|
||||
|
||||
template <typename Component>
|
||||
|
|
@ -321,7 +319,7 @@ namespace psemek::ecs
|
|||
{
|
||||
if (auto ptr = get_if<Component>())
|
||||
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 <typename Component>
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ namespace psemek::ecs
|
|||
void entity_container::remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> 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];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue