Support ecs::entity_container::batch_apply & store full entity handles (instead of entity IDs) in ECS tables
This commit is contained in:
parent
0b562a26c1
commit
72508eb445
5 changed files with 172 additions and 42 deletions
|
|
@ -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>()), ...);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue