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: support explicit or implicit query cache
|
||||
struct table_container
|
||||
{
|
||||
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>
|
||||
void apply(Function && function, component_mask const & mask);
|
||||
|
|
@ -23,12 +22,16 @@ namespace psemek::ecs::detail
|
|||
};
|
||||
|
||||
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];
|
||||
bool created = false;
|
||||
if (!result)
|
||||
{
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#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/entity_accessor.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
|
|
@ -10,6 +11,8 @@
|
|||
namespace psemek::ecs
|
||||
{
|
||||
|
||||
using query_cache = std::shared_ptr<detail::query_cache>;
|
||||
|
||||
struct entity_container
|
||||
{
|
||||
template <typename ... Components>
|
||||
|
|
@ -18,13 +21,22 @@ namespace psemek::ecs
|
|||
detail::component_uuid_holder<Components...> uuids;
|
||||
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};
|
||||
[[maybe_unused]] entity_accessor accessor = get(handle);
|
||||
|
||||
table.push_row(id);
|
||||
table->push_row(id);
|
||||
((accessor.get<Components>() = std::move(components)), ...);
|
||||
|
||||
return handle;
|
||||
|
|
@ -49,28 +61,40 @@ namespace psemek::ecs
|
|||
entity_list_.destroy(entity.id);
|
||||
}
|
||||
|
||||
template <typename ... Components, typename Function>
|
||||
void apply(Function && function)
|
||||
template <typename ... Components>
|
||||
query_cache cache()
|
||||
{
|
||||
detail::component_uuid_holder<Components...> uuids;
|
||||
|
||||
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){
|
||||
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 j = 0; j < table.component_count(); ++j)
|
||||
if (uuids.get()[i] == table.get_component_uuids()[j])
|
||||
apply_helper.pointers[i++] = table.get_component_pointers()[j];
|
||||
apply_helper.pointers[i] = entry.table->get_component_pointers()[entry.column_ids[i]];
|
||||
|
||||
for (std::size_t i = 0; i < apply_helper.size(); ++i)
|
||||
{
|
||||
apply_helper.apply(function);
|
||||
apply_helper.advance();
|
||||
}
|
||||
|
||||
}, mask);
|
||||
}
|
||||
}
|
||||
|
||||
entity_accessor get(entity_handle const & entity)
|
||||
|
|
@ -83,6 +107,7 @@ namespace psemek::ecs
|
|||
detail::entity_list entity_list_;
|
||||
mutable detail::component_index component_index_;
|
||||
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