diff --git a/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp index 401125c6..60a61781 100644 --- a/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp +++ b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp @@ -15,21 +15,63 @@ namespace psemek::ecs namespace psemek::ecs::detail { + template + void invoke(Function && function, entity_container & parent, entity_handle const & handle, Components & ... components) + { + if constexpr (std::invocable) + { + function(parent, handle, components...); + } + else if constexpr (std::invocable) + { + function(parent, components...); + } + else if constexpr (std::invocable) + { + function(handle, components...); + } + else + { + function(components...); + } + } + + template + void batch_invoke(Function && function, entity_container & parent, std::size_t count, entity_handle const * handles, Components * ... components) + { + util::span handles_span{handles, count}; + + if constexpr (std::invocable, util::span ...>) + { + function(parent, handles_span, util::span{components, count} ...); + } + else if constexpr (std::invocable, util::span ...>) + { + function(handles_span, util::span{components, count} ...); + } + else if constexpr (std::invocable ...>) + { + function(parent, util::span{components, count} ...); + } + else + { + function(util::span{components, count} ...); + } + } + template 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_ids, util::span entities) + static_apply_helper(entity_container & parent, util::span 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 @@ -41,26 +83,19 @@ namespace psemek::ecs::detail template void apply_impl(Function && function, std::index_sequence) { - 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(pointers[I].data) ...); + } - if constexpr (std::invocable) - { - function(parent, handle, *reinterpret_cast(pointers[I].data) ...); - } - else if constexpr (std::invocable) - { - function(parent, *reinterpret_cast(pointers[I].data) ...); - } - else if constexpr (std::invocable) - { - function(handle, *reinterpret_cast(pointers[I].data) ...); - } - else - { - function(*reinterpret_cast(pointers[I].data) ...); - } + template + void batch_apply(Function && function) + { + batch_apply_impl(function, std::make_index_sequence{}); + } + + template + void batch_apply_impl(Function && function, std::index_sequence) + { + batch_invoke(function, parent, row_count, entity_handles_pointer, reinterpret_cast(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()), ...); diff --git a/libs/ecs/include/psemek/ecs/detail/table.hpp b/libs/ecs/include/psemek/ecs/detail/table.hpp index 120e54ce..27ebb3db 100644 --- a/libs/ecs/include/psemek/ecs/detail/table.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table.hpp @@ -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 get_entity_ids() const + util::span get_entity_handles() const { - return entity_ids_; + return entity_handles_; } virtual ~table() = default; @@ -57,7 +57,7 @@ namespace psemek::ecs::detail std::vector component_pointers_; std::size_t row_count_ = 0; - std::vector entity_ids_; + std::vector entity_handles_; }; template @@ -66,7 +66,7 @@ namespace psemek::ecs::detail { table_impl(util::span 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 - std::size_t table_impl::push_row(entity_id id) + std::size_t table_impl::push_row(entity_handle handle) { if (row_count_ == capacity_) reallocate(); @@ -102,7 +102,7 @@ namespace psemek::ecs::detail (push_row_impl.template operator()(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()(component_pointers_[i++].data), ...); - std::swap(entity_ids_[row1], entity_ids_[row2]); + std::swap(entity_handles_[row1], entity_handles_[row2]); } template @@ -139,7 +139,7 @@ namespace psemek::ecs::detail std::size_t i = 0; (pop_row_impl.template operator()(component_pointers_[i++].data), ...); - entity_ids_.pop_back(); + entity_handles_.pop_back(); } template diff --git a/libs/ecs/include/psemek/ecs/entity_container.hpp b/libs/ecs/include/psemek/ecs/entity_container.hpp index b61085ba..bf5eccd5 100644 --- a/libs/ecs/include/psemek/ecs/entity_container.hpp +++ b/libs/ecs/include/psemek/ecs/entity_container.hpp @@ -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 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...) + // void(span, span...) + // void(entity_container, span...) + // void(entity_container, span, span...) + // 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 + 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() = std::move(components)), ...); return handle; @@ -120,7 +137,7 @@ namespace psemek::ecs for (auto const & entry : cache->tables) { - detail::static_apply_helper apply_helper(*this, entry.table->get_entity_ids(), entity_list_.get_entities()); + detail::static_apply_helper 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 + void entity_container::batch_apply(Function && function, query_cache cache) + { + if (!cache) + cache = this->cache(); + + for (auto const & entry : cache->tables) + { + detail::static_apply_helper 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); + } + } + } diff --git a/libs/ecs/source/entity_container.cpp b/libs/ecs/source/entity_container.cpp index 94324933..f7762a1c 100644 --- a/libs/ecs/source/entity_container.cpp +++ b/libs/ecs/source/entity_container.cpp @@ -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); } diff --git a/libs/ecs/tests/apply.cpp b/libs/ecs/tests/apply.cpp index ae8ad25d..670b1448 100644 --- a/libs/ecs/tests/apply.cpp +++ b/libs/ecs/tests/apply.cpp @@ -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([&](entity_container &, util::span, util::span components){ call_count += components.size(); }); + container.batch_apply([&](entity_container &, util::span components){ call_count += components.size(); }); + container.batch_apply([&](util::span, util::span components){ call_count += components.size(); }); + container.batch_apply([&](util::span 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([&](util::span components){ + count += components.size(); + for (auto & component : components) + sum += component.value; + }); + container.batch_apply([&](util::span components){ + count += components.size(); + for (auto & component : components) + sum += component.value; + }); + container.batch_apply([&](util::span components1, util::span){ + count -= components1.size(); + for (auto & component : components1) + sum -= component.value; + }); + + expect_equal(count, expected_count); + expect_equal(sum, expected_sum); +}