Support ecs::entity_container::batch_apply & store full entity handles (instead of entity IDs) in ECS tables

This commit is contained in:
Nikita Lisitsa 2023-08-23 12:58:00 +03:00
parent 0b562a26c1
commit 72508eb445
5 changed files with 172 additions and 42 deletions

View file

@ -15,21 +15,63 @@ namespace psemek::ecs
namespace psemek::ecs::detail
{
template <typename Function, typename ... Components>
void invoke(Function && function, entity_container & parent, entity_handle const & handle, Components & ... components)
{
if constexpr (std::invocable<Function, entity_container &, entity_handle, Components & ...>)
{
function(parent, handle, components...);
}
else if constexpr (std::invocable<Function, entity_container &, Components & ...>)
{
function(parent, components...);
}
else if constexpr (std::invocable<Function, entity_handle, Components & ...>)
{
function(handle, components...);
}
else
{
function(components...);
}
}
template <typename Function, typename ... Components>
void batch_invoke(Function && function, entity_container & parent, std::size_t count, entity_handle const * handles, Components * ... components)
{
util::span<entity_handle const> handles_span{handles, count};
if constexpr (std::invocable<Function, entity_container &, util::span<entity_handle const>, util::span<Components> ...>)
{
function(parent, handles_span, util::span<Components>{components, count} ...);
}
else if constexpr (std::invocable<Function, util::span<entity_handle const>, util::span<Components> ...>)
{
function(handles_span, util::span<Components>{components, count} ...);
}
else if constexpr (std::invocable<Function, entity_container &, util::span<Components> ...>)
{
function(parent, util::span<Components>{components, count} ...);
}
else
{
function(util::span<Components>{components, count} ...);
}
}
template <typename ... Components>
struct static_apply_helper
{
entity_container & parent;
std::size_t row_count;
entity_id const * entity_id_pointer;
entity_data const * entity_data_pointer;
entity_handle const * entity_handles_pointer;
// (+1) to prevent zero-sized array
table::component_pointer pointers[sizeof...(Components) + 1];
static_apply_helper(entity_container & parent, util::span<entity_id const> entity_ids, util::span<entity_data const> entities)
static_apply_helper(entity_container & parent, util::span<entity_handle const> entity_handles)
: parent(parent)
, row_count(entity_ids.size())
, entity_id_pointer(entity_ids.data())
, entity_data_pointer(entities.data())
, row_count(entity_handles.size())
, entity_handles_pointer(entity_handles.data())
{}
template <typename Function>
@ -41,26 +83,19 @@ namespace psemek::ecs::detail
template <typename Function, std::size_t ... I>
void apply_impl(Function && function, std::index_sequence<I...>)
{
auto id = *entity_id_pointer;
auto epoch = entity_data_pointer[id].epoch;
entity_handle handle{id, epoch};
invoke(function, parent, *entity_handles_pointer, *reinterpret_cast<Components *>(pointers[I].data) ...);
}
if constexpr (std::invocable<Function, entity_container &, entity_handle, Components & ...>)
{
function(parent, handle, *reinterpret_cast<Components *>(pointers[I].data) ...);
}
else if constexpr (std::invocable<Function, entity_container &, Components & ...>)
{
function(parent, *reinterpret_cast<Components *>(pointers[I].data) ...);
}
else if constexpr (std::invocable<Function, entity_handle, Components & ...>)
{
function(handle, *reinterpret_cast<Components *>(pointers[I].data) ...);
}
else
{
function(*reinterpret_cast<Components *>(pointers[I].data) ...);
}
template <typename Function>
void batch_apply(Function && function)
{
batch_apply_impl(function, std::make_index_sequence<sizeof...(Components)>{});
}
template <typename Function, std::size_t ... I>
void batch_apply_impl(Function && function, std::index_sequence<I...>)
{
batch_invoke(function, parent, row_count, entity_handles_pointer, reinterpret_cast<Components *>(pointers[I].data) ...);
}
std::size_t size() const
@ -70,7 +105,7 @@ namespace psemek::ecs::detail
void advance()
{
++entity_id_pointer;
++entity_handles_pointer;
std::size_t i = 0;
((pointers[i++].data += stride<Components>()), ...);

View file

@ -41,13 +41,13 @@ namespace psemek::ecs::detail
return row_count_;
}
virtual std::size_t push_row(entity_id id) = 0;
virtual std::size_t push_row(entity_handle handles) = 0;
virtual void swap_rows(std::size_t row1, std::size_t row2) = 0;
virtual void pop_row() = 0;
util::span<entity_id const> get_entity_ids() const
util::span<entity_handle const> get_entity_handles() const
{
return entity_ids_;
return entity_handles_;
}
virtual ~table() = default;
@ -57,7 +57,7 @@ namespace psemek::ecs::detail
std::vector<component_pointer> component_pointers_;
std::size_t row_count_ = 0;
std::vector<entity_id> entity_ids_;
std::vector<entity_handle> entity_handles_;
};
template <typename ... Components>
@ -66,7 +66,7 @@ namespace psemek::ecs::detail
{
table_impl(util::span<util::uuid const> component_uuids);
std::size_t push_row(entity_id id) override;
std::size_t push_row(entity_handle handle) override;
void swap_rows(std::size_t row1, std::size_t row2) override;
void pop_row() override;
@ -85,7 +85,7 @@ namespace psemek::ecs::detail
}
template <typename ... Components>
std::size_t table_impl<Components...>::push_row(entity_id id)
std::size_t table_impl<Components...>::push_row(entity_handle handle)
{
if (row_count_ == capacity_)
reallocate();
@ -102,7 +102,7 @@ namespace psemek::ecs::detail
(push_row_impl.template operator()<Components>(component_pointers_[i++].data), ...);
++row_count_;
entity_ids_.push_back(id);
entity_handles_.push_back(handle);
return row_count_ - 1;
}
@ -122,7 +122,7 @@ namespace psemek::ecs::detail
std::size_t i = 0;
(swap_rows_impl.template operator()<Components>(component_pointers_[i++].data), ...);
std::swap(entity_ids_[row1], entity_ids_[row2]);
std::swap(entity_handles_[row1], entity_handles_[row2]);
}
template <typename ... Components>
@ -139,7 +139,7 @@ namespace psemek::ecs::detail
std::size_t i = 0;
(pop_row_impl.template operator()<Components>(component_pointers_[i++].data), ...);
entity_ids_.pop_back();
entity_handles_.pop_back();
}
template <typename ... Components>

View file

@ -55,12 +55,29 @@ namespace psemek::ecs
query_cache cache();
// Apply a function to all entities having the specified
// components. The function signature is void(entity_handle, components...)
// components. The function signature is one of
// void(components...)
// void(entity_handle, components...)
// void(entity_container, components...)
// void(entity_container, entity_handle, components...)
// An optional query cache can be supplied to speed up the call
// UB if the cache wasn't created with the exact same component sequence
template <typename ... Components, typename Function>
void apply(Function && function, query_cache cache = {});
// Apply a function to all entities having the specified
// components. Instead of applying the function to each entity,
// the function is applied in "batch" mode, i.e. to array views
// of components. The function signature is one of
// void(span<components>...)
// void(span<entity_handle const>, span<components>...)
// void(entity_container, span<components>...)
// void(entity_container, span<entity_handle const>, span<components>...)
// An optional query cache can be supplied to speed up the call
// UB if the cache wasn't created with the exact same component sequence
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_;
@ -91,7 +108,7 @@ namespace psemek::ecs
entity_handle handle{id, entity_list_.get_entities()[id].epoch};
[[maybe_unused]] entity_accessor accessor = get(handle);
table->push_row(id);
table->push_row(handle);
((accessor.get<Components>() = std::move(components)), ...);
return handle;
@ -120,7 +137,7 @@ namespace psemek::ecs
for (auto const & entry : cache->tables)
{
detail::static_apply_helper<Components...> apply_helper(*this, entry.table->get_entity_ids(), entity_list_.get_entities());
detail::static_apply_helper<Components...> apply_helper(*this, entry.table->get_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]];
@ -132,4 +149,22 @@ namespace psemek::ecs
}
}
}
template <typename ... Components, typename Function>
void entity_container::batch_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(*this, entry.table->get_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.batch_apply(function);
}
}
}

View file

@ -13,11 +13,11 @@ namespace psemek::ecs
// Swap with the last row in that table
auto entities = entity_list_.get_entities();
auto & data = entities[entity.id];
auto table_entity_ids = data.table->get_entity_ids();
data.table->swap_rows(data.row, table_entity_ids.size() - 1);
auto table_entity_handles = data.table->get_entity_handles();
data.table->swap_rows(data.row, table_entity_handles.size() - 1);
data.table->pop_row();
auto swap_id = table_entity_ids[data.row];
auto & swap_data = entities[swap_id];
auto swap_handle = table_entity_handles[data.row];
auto & swap_data = entities[swap_handle.id];
swap_data.row = data.row;
entity_list_.destroy(entity.id);
}

View file

@ -109,3 +109,63 @@ test_case(ecs_apply_components_2)
expect_equal(count, expected_count);
expect_equal(sum, expected_sum);
}
test_case(ecs_apply_batch_invoke)
{
entity_container container;
int const count = 2048;
for (int i = 0; i < count; ++i)
container.create(component_1{i});
int call_count = 0;
container.batch_apply<component_1>([&](entity_container &, util::span<entity_handle const>, util::span<component_1> components){ call_count += components.size(); });
container.batch_apply<component_1>([&](entity_container &, util::span<component_1> components){ call_count += components.size(); });
container.batch_apply<component_1>([&](util::span<entity_handle const>, util::span<component_1> components){ call_count += components.size(); });
container.batch_apply<component_1>([&](util::span<component_1> components){ call_count += components.size(); });
expect_equal(count * 4, call_count);
}
test_case(ecs_apply_batch_components)
{
entity_container container;
random::generator rng;
int const expected_count = 1024*1024;
int expected_sum = 0;
for (int i = 0; i < expected_count; ++i)
{
int value = random::uniform(rng, -1024, 1024);
int type = random::uniform(rng, 0, 2);
if (type == 0)
container.create(component_1{value});
else if (type == 1)
container.create(component_2{value});
else if (type == 2)
container.create(component_1{value}, component_2{value});
expected_sum += value;
}
int count = 0;
int sum = 0;
container.batch_apply<component_1>([&](util::span<component_1 const> components){
count += components.size();
for (auto & component : components)
sum += component.value;
});
container.batch_apply<component_2>([&](util::span<component_2 const> components){
count += components.size();
for (auto & component : components)
sum += component.value;
});
container.batch_apply<component_1, component_2>([&](util::span<component_1 const> components1, util::span<component_2 const>){
count -= components1.size();
for (auto & component : components1)
sum -= component.value;
});
expect_equal(count, expected_count);
expect_equal(sum, expected_sum);
}