ECS library wip: support explicit query cache
This commit is contained in:
parent
e0e0df8128
commit
c1991cbb57
6 changed files with 280 additions and 16 deletions
26
libs/ecs/include/psemek/ecs/detail/query_cache.hpp
Normal file
26
libs/ecs/include/psemek/ecs/detail/query_cache.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/util/uuid.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace psemek::ecs::detail
|
||||||
|
{
|
||||||
|
|
||||||
|
struct table;
|
||||||
|
|
||||||
|
struct query_cache_entry
|
||||||
|
{
|
||||||
|
struct table * table = nullptr;
|
||||||
|
std::vector<std::size_t> column_ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct query_cache
|
||||||
|
{
|
||||||
|
std::vector<util::uuid> component_uuids;
|
||||||
|
std::vector<query_cache_entry> tables;
|
||||||
|
|
||||||
|
void add(table * table);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
50
libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp
Normal file
50
libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ecs/detail/query_cache.hpp>
|
||||||
|
#include <psemek/ecs/detail/component_mask.hpp>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace psemek::ecs::detail
|
||||||
|
{
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
auto result = std::make_shared<query_cache>();
|
||||||
|
result->component_uuids.assign(component_uuids.begin(), component_uuids.end());
|
||||||
|
get(mask).emplace_back(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Function>
|
||||||
|
void apply(Function && function, component_mask const & mask)
|
||||||
|
{
|
||||||
|
for (auto & caches : caches_)
|
||||||
|
{
|
||||||
|
if (!util::is_subset(caches.first, mask)) continue;
|
||||||
|
filter(caches.second);
|
||||||
|
for (auto & cache : caches.second)
|
||||||
|
function(*cache.lock());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<component_mask, std::vector<std::weak_ptr<query_cache>>> 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -9,11 +9,10 @@ namespace psemek::ecs::detail
|
||||||
{
|
{
|
||||||
|
|
||||||
// TODO: store tables in a bitmask trie balanced by subtree size
|
// TODO: store tables in a bitmask trie balanced by subtree size
|
||||||
// TODO: support explicit or implicit query cache
|
|
||||||
struct table_container
|
struct table_container
|
||||||
{
|
{
|
||||||
template <typename ... Components>
|
template <typename ... Components>
|
||||||
table_impl<Components...> & insert(component_mask const & mask, util::span<util::uuid const> component_uuids);
|
std::pair<table_impl<Components...> *, bool> insert(component_mask const & mask, util::span<util::uuid const> component_uuids);
|
||||||
|
|
||||||
template <typename Function>
|
template <typename Function>
|
||||||
void apply(Function && function, component_mask const & mask);
|
void apply(Function && function, component_mask const & mask);
|
||||||
|
|
@ -23,12 +22,16 @@ namespace psemek::ecs::detail
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename ... Components>
|
template <typename ... Components>
|
||||||
table_impl<Components...> & table_container::insert(component_mask const & mask, util::span<util::uuid const> component_uuids)
|
std::pair<table_impl<Components...> *, bool> table_container::insert(component_mask const & mask, util::span<util::uuid const> component_uuids)
|
||||||
{
|
{
|
||||||
auto & result = tables_[mask];
|
auto & result = tables_[mask];
|
||||||
|
bool created = false;
|
||||||
if (!result)
|
if (!result)
|
||||||
|
{
|
||||||
result = std::make_unique<table_impl<Components...>>(component_uuids);
|
result = std::make_unique<table_impl<Components...>>(component_uuids);
|
||||||
return *static_cast<table_impl<Components...> *>(result.get());
|
created = true;
|
||||||
|
}
|
||||||
|
return {static_cast<table_impl<Components...> *>(result.get()), created};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Function>
|
template <typename Function>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <psemek/ecs/detail/component_index.hpp>
|
#include <psemek/ecs/detail/component_index.hpp>
|
||||||
#include <psemek/ecs/detail/entity_list.hpp>
|
#include <psemek/ecs/detail/entity_list.hpp>
|
||||||
#include <psemek/ecs/detail/table_container.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/apply_helper.hpp>
|
||||||
#include <psemek/ecs/entity_accessor.hpp>
|
#include <psemek/ecs/entity_accessor.hpp>
|
||||||
#include <psemek/util/span.hpp>
|
#include <psemek/util/span.hpp>
|
||||||
|
|
@ -10,6 +11,8 @@
|
||||||
namespace psemek::ecs
|
namespace psemek::ecs
|
||||||
{
|
{
|
||||||
|
|
||||||
|
using query_cache = std::shared_ptr<detail::query_cache>;
|
||||||
|
|
||||||
struct entity_container
|
struct entity_container
|
||||||
{
|
{
|
||||||
template <typename ... Components>
|
template <typename ... Components>
|
||||||
|
|
@ -18,13 +21,22 @@ namespace psemek::ecs
|
||||||
detail::component_uuid_holder<Components...> uuids;
|
detail::component_uuid_holder<Components...> uuids;
|
||||||
detail::component_mask mask = component_index_.make_component_mask(uuids.get());
|
detail::component_mask mask = component_index_.make_component_mask(uuids.get());
|
||||||
|
|
||||||
auto & table = table_container_.insert<Components...>(mask, uuids.get());
|
auto insert_result = table_container_.insert<Components...>(mask, uuids.get());
|
||||||
|
auto table = insert_result.first;
|
||||||
|
bool created = insert_result.second;
|
||||||
|
|
||||||
auto id = entity_list_.create(&table, table.row_count());
|
if (created)
|
||||||
|
{
|
||||||
|
query_cache_container_.apply([table](detail::query_cache & cache){
|
||||||
|
cache.add(table);
|
||||||
|
}, mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto id = entity_list_.create(table, table->row_count());
|
||||||
entity_handle handle{id, entity_list_.get_entities()[id].epoch};
|
entity_handle handle{id, entity_list_.get_entities()[id].epoch};
|
||||||
[[maybe_unused]] entity_accessor accessor = get(handle);
|
[[maybe_unused]] entity_accessor accessor = get(handle);
|
||||||
|
|
||||||
table.push_row(id);
|
table->push_row(id);
|
||||||
((accessor.get<Components>() = std::move(components)), ...);
|
((accessor.get<Components>() = std::move(components)), ...);
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
|
|
@ -49,28 +61,40 @@ namespace psemek::ecs
|
||||||
entity_list_.destroy(entity.id);
|
entity_list_.destroy(entity.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ... Components, typename Function>
|
template <typename ... Components>
|
||||||
void apply(Function && function)
|
query_cache cache()
|
||||||
{
|
{
|
||||||
detail::component_uuid_holder<Components...> uuids;
|
detail::component_uuid_holder<Components...> uuids;
|
||||||
|
|
||||||
detail::component_mask mask = component_index_.make_component_mask(uuids.get());
|
detail::component_mask mask = component_index_.make_component_mask(uuids.get());
|
||||||
|
|
||||||
|
auto result = query_cache_container_.create(mask, uuids.get());
|
||||||
|
|
||||||
table_container_.apply([&](detail::table & table){
|
table_container_.apply([&](detail::table & table){
|
||||||
detail::static_apply_helper<Components...> apply_helper(table.get_entity_ids(), entity_list_.get_entities());
|
result->add(&table);
|
||||||
|
}, mask);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ... Components, typename Function>
|
||||||
|
void apply(Function && function, query_cache cache = {})
|
||||||
|
{
|
||||||
|
if (!cache)
|
||||||
|
cache = this->cache<Components...>();
|
||||||
|
|
||||||
|
for (auto const & entry : cache->tables)
|
||||||
|
{
|
||||||
|
detail::static_apply_helper<Components...> apply_helper(entry.table->get_entity_ids(), entity_list_.get_entities());
|
||||||
|
|
||||||
for (std::size_t i = 0; i < sizeof...(Components); ++i)
|
for (std::size_t i = 0; i < sizeof...(Components); ++i)
|
||||||
for (std::size_t j = 0; j < table.component_count(); ++j)
|
apply_helper.pointers[i] = entry.table->get_component_pointers()[entry.column_ids[i]];
|
||||||
if (uuids.get()[i] == table.get_component_uuids()[j])
|
|
||||||
apply_helper.pointers[i++] = table.get_component_pointers()[j];
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < apply_helper.size(); ++i)
|
for (std::size_t i = 0; i < apply_helper.size(); ++i)
|
||||||
{
|
{
|
||||||
apply_helper.apply(function);
|
apply_helper.apply(function);
|
||||||
apply_helper.advance();
|
apply_helper.advance();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, mask);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entity_accessor get(entity_handle const & entity)
|
entity_accessor get(entity_handle const & entity)
|
||||||
|
|
@ -83,6 +107,7 @@ namespace psemek::ecs
|
||||||
detail::entity_list entity_list_;
|
detail::entity_list entity_list_;
|
||||||
mutable detail::component_index component_index_;
|
mutable detail::component_index component_index_;
|
||||||
detail::table_container table_container_;
|
detail::table_container table_container_;
|
||||||
|
detail::query_cache_container query_cache_container_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
libs/ecs/source/detail/query_cache.cpp
Normal file
25
libs/ecs/source/detail/query_cache.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include <psemek/ecs/detail/query_cache.hpp>
|
||||||
|
#include <psemek/ecs/detail/table.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ecs::detail
|
||||||
|
{
|
||||||
|
|
||||||
|
void query_cache::add(table * table)
|
||||||
|
{
|
||||||
|
auto & entry = tables.emplace_back();
|
||||||
|
entry.table = table;
|
||||||
|
|
||||||
|
for (auto const & uuid : component_uuids)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < table->component_count(); ++i)
|
||||||
|
{
|
||||||
|
if (uuid == table->get_component_uuids()[i])
|
||||||
|
{
|
||||||
|
entry.column_ids.push_back(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
135
libs/ecs/tests/cache.cpp
Normal file
135
libs/ecs/tests/cache.cpp
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
#include <psemek/test/test.hpp>
|
||||||
|
|
||||||
|
#include <psemek/ecs/entity_container.hpp>
|
||||||
|
#include <psemek/random/generator.hpp>
|
||||||
|
#include <psemek/random/uniform.hpp>
|
||||||
|
|
||||||
|
using namespace psemek;
|
||||||
|
using namespace psemek::ecs;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct component_1
|
||||||
|
{
|
||||||
|
int value;
|
||||||
|
|
||||||
|
static constexpr util::uuid uuid()
|
||||||
|
{
|
||||||
|
return {1, 0};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct component_2
|
||||||
|
{
|
||||||
|
int value;
|
||||||
|
|
||||||
|
static constexpr util::uuid uuid()
|
||||||
|
{
|
||||||
|
return {2, 0};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(ecs_cache_empty)
|
||||||
|
{
|
||||||
|
entity_container container;
|
||||||
|
|
||||||
|
container.create();
|
||||||
|
container.create(component_1{10});
|
||||||
|
container.create(component_2{20});
|
||||||
|
container.create(component_1{100}, component_2{200});
|
||||||
|
|
||||||
|
auto cache = container.cache();
|
||||||
|
|
||||||
|
expect_different_ptr(cache.get(), nullptr);
|
||||||
|
expect(cache->component_uuids.empty());
|
||||||
|
expect_equal(cache->tables.size(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(ecs_cache_components)
|
||||||
|
{
|
||||||
|
entity_container container;
|
||||||
|
|
||||||
|
container.create();
|
||||||
|
container.create(component_1{10});
|
||||||
|
container.create(component_2{20});
|
||||||
|
container.create(component_1{100}, component_2{200});
|
||||||
|
|
||||||
|
auto cache = container.cache<component_1>();
|
||||||
|
|
||||||
|
expect_different_ptr(cache.get(), nullptr);
|
||||||
|
expect_equal(cache->component_uuids.size(), 1);
|
||||||
|
expect_equal(cache->tables.size(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(ecs_cache_update)
|
||||||
|
{
|
||||||
|
entity_container container;
|
||||||
|
|
||||||
|
auto cache = container.cache<component_1>();
|
||||||
|
|
||||||
|
expect_different_ptr(cache.get(), nullptr);
|
||||||
|
expect_equal(cache->component_uuids.size(), 1);
|
||||||
|
expect_equal(cache->tables.size(), 0);
|
||||||
|
|
||||||
|
container.create();
|
||||||
|
expect_equal(cache->component_uuids.size(), 1);
|
||||||
|
expect_equal(cache->tables.size(), 0);
|
||||||
|
|
||||||
|
container.create(component_1{10});
|
||||||
|
expect_equal(cache->component_uuids.size(), 1);
|
||||||
|
expect_equal(cache->tables.size(), 1);
|
||||||
|
|
||||||
|
container.create(component_2{20});
|
||||||
|
expect_equal(cache->component_uuids.size(), 1);
|
||||||
|
expect_equal(cache->tables.size(), 1);
|
||||||
|
|
||||||
|
container.create(component_1{100}, component_2{200});
|
||||||
|
expect_equal(cache->component_uuids.size(), 1);
|
||||||
|
expect_equal(cache->tables.size(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(ecs_cache_apply)
|
||||||
|
{
|
||||||
|
entity_container container;
|
||||||
|
|
||||||
|
auto cache = container.cache<component_1>();
|
||||||
|
|
||||||
|
int call_count = 0;
|
||||||
|
auto counter = [&](entity_handle const &, component_1 const &){ ++call_count; };
|
||||||
|
|
||||||
|
int count_0 = 16;
|
||||||
|
int count_1 = 32;
|
||||||
|
int count_2 = 64;
|
||||||
|
int count_12 = 128;
|
||||||
|
|
||||||
|
call_count = 0;
|
||||||
|
container.apply<component_1>(counter, cache);
|
||||||
|
expect_equal(call_count, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < count_0; ++i)
|
||||||
|
container.create();
|
||||||
|
call_count = 0;
|
||||||
|
container.apply<component_1>(counter, cache);
|
||||||
|
expect_equal(call_count, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < count_1; ++i)
|
||||||
|
container.create(component_1{10});
|
||||||
|
call_count = 0;
|
||||||
|
container.apply<component_1>(counter, cache);
|
||||||
|
expect_equal(call_count, count_1);
|
||||||
|
|
||||||
|
for (int i = 0; i < count_2; ++i)
|
||||||
|
container.create(component_2{20});
|
||||||
|
call_count = 0;
|
||||||
|
container.apply<component_1>(counter, cache);
|
||||||
|
expect_equal(call_count, count_1);
|
||||||
|
|
||||||
|
for (int i = 0; i < count_12; ++i)
|
||||||
|
container.create(component_1{100}, component_2{200});
|
||||||
|
call_count = 0;
|
||||||
|
container.apply<component_1>(counter, cache);
|
||||||
|
expect_equal(call_count, count_1 + count_12);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue