diff --git a/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp index 60a61781..17ba12bf 100644 --- a/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp +++ b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp @@ -66,7 +66,7 @@ namespace psemek::ecs::detail std::size_t row_count; entity_handle const * entity_handles_pointer; // (+1) to prevent zero-sized array - table::component_pointer pointers[sizeof...(Components) + 1]; + std::uint8_t * pointers[sizeof...(Components) + 1]; static_apply_helper(entity_container & parent, util::span entity_handles) : parent(parent) @@ -83,7 +83,7 @@ namespace psemek::ecs::detail template void apply_impl(Function && function, std::index_sequence) { - invoke(function, parent, *entity_handles_pointer, *reinterpret_cast(pointers[I].data) ...); + invoke(function, parent, *entity_handles_pointer, *reinterpret_cast(pointers[I]) ...); } template @@ -95,7 +95,7 @@ namespace psemek::ecs::detail template void batch_apply_impl(Function && function, std::index_sequence) { - batch_invoke(function, parent, row_count, entity_handles_pointer, reinterpret_cast(pointers[I].data) ...); + batch_invoke(function, parent, row_count, entity_handles_pointer, reinterpret_cast(pointers[I]) ...); } std::size_t size() const @@ -108,7 +108,7 @@ namespace psemek::ecs::detail ++entity_handles_pointer; std::size_t i = 0; - ((pointers[i++].data += stride()), ...); + ((pointers[i++] += stride()), ...); } }; diff --git a/libs/ecs/include/psemek/ecs/detail/column.hpp b/libs/ecs/include/psemek/ecs/detail/column.hpp new file mode 100644 index 00000000..b6bcf46c --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/column.hpp @@ -0,0 +1,219 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::ecs::detail +{ + + struct column + { + column(util::uuid const & uuid) + : uuid_(uuid) + {} + + std::uint8_t * data() + { + return data_; + } + + util::uuid const & uuid() const + { + return uuid_; + } + + virtual void push_row() = 0; + virtual void emplace_rows(std::uint8_t * data, std::size_t count) = 0; + virtual void swap_rows(std::size_t row1, std::size_t row2) = 0; + virtual void pop_row() = 0; + virtual void clear() = 0; + + virtual std::unique_ptr clone() const = 0; + + virtual ~column() = default; + + protected: + std::uint8_t * data_ = nullptr; + util::uuid uuid_; + }; + + template > + struct column_impl + : column + { + column_impl(); + + void push_row() override; + void emplace_rows(std::uint8_t * data, std::size_t count) override; + void swap_rows(std::size_t row1, std::size_t row2) override; + void pop_row() override; + void clear() override; + + std::unique_ptr clone() const override; + + ~column_impl() override; + + private: + std::size_t capacity_ = 0; + std::size_t row_count_ = 0; + + void allocate(std::size_t min_capacity); + }; + + template + struct column_impl + : column + { + column_impl(); + + void push_row() override; + void emplace_rows(std::uint8_t * data, std::size_t count) override; + void swap_rows(std::size_t row1, std::size_t row2) override; + void pop_row() override; + void clear() override; + + std::unique_ptr clone() const override; + + ~column_impl() override; + }; + + template + column_impl::column_impl() + : column(Component::uuid()) + {} + + template + void column_impl::push_row() + { + allocate(row_count_ + 1); + + new (reinterpret_cast(data_) + row_count_) Component{}; + ++row_count_; + } + + template + void column_impl::emplace_rows(std::uint8_t * data, std::size_t count) + { + allocate(row_count_ + count); + + auto src = reinterpret_cast(data); + auto dst = reinterpret_cast(data_); + auto src_end = src + count; + while (src != src_end) + { + new (dst) Component{std::move(*src)}; + ++src; + ++dst; + } + + row_count_ += count; + } + + template + void column_impl::swap_rows(std::size_t row1, std::size_t row2) + { + auto data = reinterpret_cast(data_); + std::swap(data[row1], data[row2]); + } + + template + void column_impl::pop_row() + { + (reinterpret_cast(data_) + row_count_ - 1)->~Component(); + --row_count_; + } + + template + void column_impl::clear() + { + auto data = reinterpret_cast(data_); + for (auto p = data; p < data + row_count_; ++p) + p->~Component(); + row_count_ = 0; + } + + template + std::unique_ptr column_impl::clone() const + { + return std::make_unique>(); + } + + template + column_impl::~column_impl() + { + clear(); + delete [] data_; + data_ = nullptr; + row_count_ = 0; + } + + template + void column_impl::allocate(std::size_t min_capacity) + { + if (capacity_ >= min_capacity) + return; + + std::size_t new_capacity = std::max(64, capacity_); + while (new_capacity < min_capacity) + new_capacity *= 2; + + auto new_data = new (std::align_val_t(alignof(Component))) std::uint8_t[new_capacity * sizeof(Component)]; + + auto old_begin = reinterpret_cast(data_); + auto old_end = old_begin + row_count_; + auto new_begin = reinterpret_cast(new_data); + for (; old_begin != old_end; ++old_begin, ++new_begin) + { + new (new_begin) Component{std::move(*old_begin)}; + old_begin->~Component(); + } + + delete [] data_; + data_ = new_data; + capacity_ = new_capacity; + } + + template + column_impl::column_impl() + : column(Component::uuid()) + { + data_ = reinterpret_cast(new Component[1]); + } + + template + void column_impl::push_row() + {} + + template + void column_impl::emplace_rows(std::uint8_t *, std::size_t) + {} + + template + void column_impl::swap_rows(std::size_t, std::size_t) + {} + + template + void column_impl::pop_row() + {} + + template + void column_impl::clear() + {} + + template + std::unique_ptr column_impl::clone() const + { + return std::make_unique>(); + } + + template + column_impl::~column_impl() + { + delete [] reinterpret_cast(data_); + data_ = nullptr; + } + + +} diff --git a/libs/ecs/include/psemek/ecs/detail/component_hash.hpp b/libs/ecs/include/psemek/ecs/detail/component_hash.hpp index 53c5a9f3..534e9307 100644 --- a/libs/ecs/include/psemek/ecs/detail/component_hash.hpp +++ b/libs/ecs/include/psemek/ecs/detail/component_hash.hpp @@ -1,18 +1,28 @@ #pragma once #include -#include #include namespace psemek::ecs::detail { - inline std::size_t component_hash(util::span const & uuids) + struct component_hasher { std::size_t result = 0xcd5694d2b3f3443eull; - for (auto const & uuid : uuids) + + void operator()(util::uuid const & uuid) + { result ^= std::hash{}(uuid); - return result; + } + }; + + template + std::size_t component_hash(UUIDs const & uuids) + { + component_hasher hasher; + for (auto const & uuid : uuids) + hasher(uuid); + return hasher.result; } } diff --git a/libs/ecs/include/psemek/ecs/detail/query_cache.hpp b/libs/ecs/include/psemek/ecs/detail/query_cache.hpp index ef3995ef..d4b30dde 100644 --- a/libs/ecs/include/psemek/ecs/detail/query_cache.hpp +++ b/libs/ecs/include/psemek/ecs/detail/query_cache.hpp @@ -8,17 +8,18 @@ namespace psemek::ecs::detail { struct table; + struct column; struct query_cache_entry { struct table * table = nullptr; - std::vector column_ids; + std::vector columns; }; struct query_cache { std::vector component_uuids; - std::vector tables; + std::vector entries; void add(table * table); }; diff --git a/libs/ecs/include/psemek/ecs/detail/table.hpp b/libs/ecs/include/psemek/ecs/detail/table.hpp index 8cfd14bf..ac00a186 100644 --- a/libs/ecs/include/psemek/ecs/detail/table.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table.hpp @@ -1,8 +1,8 @@ #pragma once #include -#include #include +#include #include #include #include @@ -19,308 +19,62 @@ namespace psemek::ecs::detail struct table { - struct component_pointer - { - std::uint8_t * data = nullptr; - }; - - struct iteration_data - { - std::size_t current_row = 0; - }; + table(std::vector> columns); std::size_t hash() const { return hash_; } - std::optional component_column(util::uuid const & uuid) const - { - if (auto it = component_uuid_to_column_.find(uuid); it != component_uuid_to_column_.end()) - return it->second; - return std::nullopt; - } - - util::span component_pointers() const - { - return component_pointers_; - } + detail::column * column(util::uuid const & uuid) const; util::span entity_handles() const { return entity_handles_; } - std::size_t component_count() const + std::size_t column_count() const { - return component_pointers_.size(); + return component_uuid_to_column_.size(); } std::size_t row_count() const { - return row_count_; + return entity_handles_.size(); } - 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; + std::size_t push_row(entity_handle handle); + void swap_rows(std::size_t row1, std::size_t row2); + void pop_row(); + void clear(); - virtual void clear() = 0; + struct iteration_data + { + std::size_t current_row = 0; + }; std::optional & get_iteration_data() { return iteration_data_; } - void push_remove(std::uint32_t row) - { - remove_queue_.push_back(row); - } + void push_remove(std::uint32_t row); + std::vector grab_remove_queue(); - std::vector grab_remove_queue() - { - return std::move(remove_queue_); - } + std::unique_ptr clone() const; - table * get_delayed_table() - { - if (!delayed_table_) - delayed_table_ = create_delayed_table(); - return delayed_table_.get(); - } - - virtual util::span flush_delayed() = 0; - - virtual ~table() = default; + table * get_delayed_table(); + util::span flush_delayed(); protected: std::size_t hash_; - std::vector component_uuids_; - util::hash_map component_uuid_to_column_; - std::vector component_pointers_; - std::size_t row_count_ = 0; + util::hash_map> component_uuid_to_column_; std::vector entity_handles_; std::optional iteration_data_; - std::vector remove_queue_; - std::unique_ptr
delayed_table_; - - virtual std::unique_ptr
create_delayed_table() = 0; }; - template - struct table_impl - : table - { - table_impl(util::span component_uuids); - - 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; - - void clear() override; - - util::span flush_delayed() override; - - ~table_impl() override; - - private: - std::size_t capacity_ = 0; - - void reallocate(); - - std::unique_ptr
create_delayed_table() override; - }; - - template - table_impl::table_impl(util::span component_uuids) - { - assert(sizeof...(Components) == component_uuids.size()); - hash_ = component_hash(component_uuids); - component_uuids_.assign(component_uuids.begin(), component_uuids.end()); - component_pointers_.resize(sizeof...(Components)); - for (std::size_t i = 0; i < component_uuids.size(); ++i) - component_uuid_to_column_.insert({component_uuids[i], i}); - } - - template - std::size_t table_impl::push_row(entity_handle handle) - { - if (row_count_ == capacity_) - reallocate(); - - [[maybe_unused]] auto push_row_impl = [&](std::uint8_t * data) - { - if constexpr (!detail::is_empty_v) - { - new (reinterpret_cast(data) + row_count_) Component{}; - } - }; - - std::size_t i = 0; - (push_row_impl.template operator()(component_pointers_[i++].data), ...); - ++row_count_; - - entity_handles_.push_back(handle); - - return row_count_ - 1; - } - - template - void table_impl::swap_rows(std::size_t row1, std::size_t row2) - { - [[maybe_unused]] auto swap_rows_impl = [&](std::uint8_t * data) - { - if constexpr (!detail::is_empty_v) - { - auto cdata = reinterpret_cast(data); - std::iter_swap(cdata + row1, cdata + row2); - } - }; - - std::size_t i = 0; - (swap_rows_impl.template operator()(component_pointers_[i++].data), ...); - - std::swap(entity_handles_[row1], entity_handles_[row2]); - } - - template - void table_impl::pop_row() - { - --row_count_; - [[maybe_unused]] auto pop_row_impl = [&](std::uint8_t * data) - { - if constexpr (!detail::is_empty_v) - { - (reinterpret_cast(data) + row_count_)->~Component(); - } - }; - - std::size_t i = 0; - (pop_row_impl.template operator()(component_pointers_[i++].data), ...); - entity_handles_.pop_back(); - } - - template - void table_impl::clear() - { - [[maybe_unused]] auto clear_column_impl = [&](std::uint8_t * data) - { - auto ptr = reinterpret_cast(data); - if constexpr (!detail::is_empty_v) - { - for (std::size_t i = 0; i < row_count_; ++i) - ptr[i].~Component(); - } - }; - - std::size_t i = 0; - (clear_column_impl.template operator()(component_pointers_[i++].data), ...); - - row_count_ = 0; - entity_handles_.clear(); - } - - template - util::span table_impl::flush_delayed() - { - if (!delayed_table_) - return {}; - - auto delayed = static_cast *>(delayed_table_.get()); - - auto old_row_count = row_count_; - - while (capacity_ < row_count_ + delayed->row_count_) - reallocate(); - - std::size_t delayed_row = 0; - [[maybe_unused]] auto move_row_impl = [&](std::uint8_t * data_tgt, std::uint8_t * data_src) - { - if constexpr (!detail::is_empty_v) - { - new (reinterpret_cast(data_tgt) + row_count_) Component{std::move(*(reinterpret_cast(data_src) + delayed_row))}; - } - }; - - for (; delayed_row < delayed->row_count_; ++delayed_row) - { - std::size_t i1 = 0, i2 = 0; - (move_row_impl.template operator()(component_pointers_[i1++].data, delayed->component_pointers_[i2++].data), ...); - ++row_count_; - } - - entity_handles_.insert(entity_handles_.end(), delayed->entity_handles_.begin(), delayed->entity_handles_.end()); - - delayed->clear(); - - return {entity_handles_.data() + old_row_count, entity_handles_.data() + row_count_}; - } - - template - table_impl::~table_impl() - { - clear(); - - [[maybe_unused]] auto delete_column_impl = [&](std::uint8_t * & data) - { - if constexpr (detail::is_empty_v) - { - reinterpret_cast(data)->~Component(); - } - delete [] data; - data = nullptr; - }; - - std::size_t i = 0; - (delete_column_impl.template operator()(component_pointers_[i++].data), ...); - - capacity_ = 0; - } - - template - void table_impl::reallocate() - { - std::size_t const new_capacity = capacity_ == 0 ? 64 : capacity_ * 2; - - [[maybe_unused]] auto reallocate_impl = [&](std::uint8_t * & data) - { - if constexpr (detail::is_empty_v) - { - if (!data) - data = reinterpret_cast(new Component[1]); - } - else - { - auto new_data = new (std::align_val_t(alignof(Component))) std::uint8_t[new_capacity * sizeof(Component)]; - - auto old_begin = reinterpret_cast(data); - auto old_end = old_begin + row_count_; - auto new_begin = reinterpret_cast(new_data); - for (; old_begin != old_end; ++old_begin, ++new_begin) - { - new (new_begin) Component{std::move(*old_begin)}; - old_begin->~Component(); - } - - delete [] data; - data = new_data; - } - }; - - std::size_t i = 0; - (reallocate_impl.template operator()(component_pointers_[i++].data), ...); - - capacity_ = new_capacity; - } - - template - std::unique_ptr
table_impl::create_delayed_table() - { - return std::make_unique>(component_uuids_); - } - } diff --git a/libs/ecs/include/psemek/ecs/detail/table_container.hpp b/libs/ecs/include/psemek/ecs/detail/table_container.hpp index 78bf05ce..72a4c332 100644 --- a/libs/ecs/include/psemek/ecs/detail/table_container.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table_container.hpp @@ -26,10 +26,10 @@ namespace psemek::ecs::detail { bool operator()(util::span const & uuids, std::unique_ptr
const & table) const { - if (uuids.size() != table->component_count()) + if (uuids.size() != table->column_count()) return false; for (auto const & uuid : uuids) - if (!table->component_column(uuid)) + if (!table->column(uuid)) return false; return true; } @@ -60,7 +60,10 @@ namespace psemek::ecs::detail if (it != tables_.end()) return {it->get(), false}; - auto table = std::make_unique>(component_uuids); + std::vector> columns; + (columns.push_back(std::make_unique>()), ...); + + auto table = std::make_unique(std::move(columns)); auto result = table.get(); tables_.insert(std::move(table)); return {result, true}; @@ -73,7 +76,7 @@ namespace psemek::ecs::detail { bool good = true; for (auto const & uuid : component_uuids) - if (!table->component_column(uuid)) + if (!table->column(uuid)) { good = false; break; diff --git a/libs/ecs/include/psemek/ecs/entity_container.hpp b/libs/ecs/include/psemek/ecs/entity_container.hpp index 32dcea16..0c2dd065 100644 --- a/libs/ecs/include/psemek/ecs/entity_container.hpp +++ b/libs/ecs/include/psemek/ecs/entity_container.hpp @@ -192,6 +192,8 @@ namespace psemek::ecs detail::table_container table_container_; detail::query_cache_container query_cache_container_; + std::vector uuid_helper_; + void remove_row(detail::table & table, std::uint32_t row, util::span entities); }; @@ -210,7 +212,7 @@ namespace psemek::ecs { query_cache_container_.apply([table](detail::query_cache & cache){ cache.add(table); - }, [table](util::uuid const & uuid){ return table->component_column(uuid) != std::nullopt; }); + }, [table](util::uuid const & uuid){ return table->column(uuid) != nullptr; }); } if (table->get_iteration_data()) @@ -246,14 +248,14 @@ namespace psemek::ecs if (!cache) cache = this->cache(); - for (auto const & entry : cache->tables) + for (auto const & entry : cache->entries) { auto & iteration_data = entry.table->get_iteration_data(); iteration_data.emplace(); detail::static_apply_helper apply_helper(*this, entry.table->entity_handles()); for (std::size_t i = 0; i < sizeof...(Components); ++i) - apply_helper.pointers[i] = entry.table->component_pointers()[entry.column_ids[i]]; + apply_helper.pointers[i] = entry.table->column(cache->component_uuids[i])->data(); for (std::size_t i = 0; i < entry.table->row_count(); ++i) { @@ -286,12 +288,12 @@ namespace psemek::ecs if (!cache) cache = this->cache(); - for (auto const & entry : cache->tables) + for (auto const & entry : cache->entries) { detail::static_apply_helper apply_helper(*this, entry.table->entity_handles()); for (std::size_t i = 0; i < sizeof...(Components); ++i) - apply_helper.pointers[i] = entry.table->component_pointers()[entry.column_ids[i]]; + apply_helper.pointers[i] = entry.table->column(cache->component_uuids[i])->data(); apply_helper.batch_apply(function); } @@ -307,11 +309,11 @@ namespace psemek::ecs { util::uuid const uuid = Component::uuid(); - auto column = table_->component_column(uuid); + auto column = table_->column(uuid); if (!column) return nullptr; - return reinterpret_cast(table_->component_pointers()[*column].data + detail::stride() * row_); + return reinterpret_cast(column->data() + detail::stride() * row_); } template diff --git a/libs/ecs/source/detail/query_cache.cpp b/libs/ecs/source/detail/query_cache.cpp index 5c472208..464ce9cc 100644 --- a/libs/ecs/source/detail/query_cache.cpp +++ b/libs/ecs/source/detail/query_cache.cpp @@ -6,11 +6,11 @@ namespace psemek::ecs::detail void query_cache::add(table * table) { - auto & entry = tables.emplace_back(); + auto & entry = entries.emplace_back(); entry.table = table; for (auto const & uuid : component_uuids) - entry.column_ids.push_back(*(table->component_column(uuid))); + entry.columns.push_back(table->column(uuid)); } } diff --git a/libs/ecs/source/detail/table.cpp b/libs/ecs/source/detail/table.cpp new file mode 100644 index 00000000..71b2f808 --- /dev/null +++ b/libs/ecs/source/detail/table.cpp @@ -0,0 +1,98 @@ +#include +#include + +namespace psemek::ecs::detail +{ + + table::table(std::vector> columns) + { + component_hasher hasher; + for (auto & column : columns) + { + auto uuid = column->uuid(); + hasher(uuid); + component_uuid_to_column_.insert({uuid, std::move(column)}); + } + hash_ = hasher.result; + } + + detail::column * table::column(util::uuid const & uuid) const + { + if (auto it = component_uuid_to_column_.find(uuid); it != component_uuid_to_column_.end()) + return it->second.get(); + return nullptr; + } + + std::size_t table::push_row(entity_handle handle) + { + for (auto & pair : component_uuid_to_column_) + pair.second->push_row(); + entity_handles_.push_back(handle); + return entity_handles_.size() - 1; + } + + void table::swap_rows(std::size_t row1, std::size_t row2) + { + for (auto & pair : component_uuid_to_column_) + pair.second->swap_rows(row1, row2); + std::swap(entity_handles_[row1], entity_handles_[row2]); + } + + void table::pop_row() + { + for (auto & pair : component_uuid_to_column_) + pair.second->pop_row(); + entity_handles_.pop_back(); + } + + void table::clear() + { + for (auto & pair : component_uuid_to_column_) + pair.second->clear(); + entity_handles_.clear(); + } + + void table::push_remove(std::uint32_t row) + { + remove_queue_.push_back(row); + } + + std::vector table::grab_remove_queue() + { + return std::move(remove_queue_); + } + + std::unique_ptr
table::clone() const + { + std::vector> columns; + for (auto const & pair : component_uuid_to_column_) + columns.push_back(pair.second->clone()); + return std::make_unique
(std::move(columns)); + } + + table * table::get_delayed_table() + { + if (!delayed_table_) + delayed_table_ = clone(); + return delayed_table_.get(); + } + + util::span table::flush_delayed() + { + if (!delayed_table_) + return {}; + + std::size_t count = delayed_table_->row_count(); + for (auto & pair : component_uuid_to_column_) + { + auto * src_column = delayed_table_->column(pair.second->uuid()); + pair.second->emplace_rows(src_column->data(), count); + } + entity_handles_.insert(entity_handles_.end(), delayed_table_->entity_handles_.begin(), delayed_table_->entity_handles_.end()); + + delayed_table_->clear(); + + return {entity_handles_.data() + entity_handles_.size() - count, count}; + } + +} diff --git a/libs/ecs/tests/cache.cpp b/libs/ecs/tests/cache.cpp index 536832f4..3e558246 100644 --- a/libs/ecs/tests/cache.cpp +++ b/libs/ecs/tests/cache.cpp @@ -45,7 +45,7 @@ test_case(ecs_cache_empty) expect_different_ptr(cache.get(), nullptr); expect(cache->component_uuids.empty()); - expect_equal(cache->tables.size(), 4); + expect_equal(cache->entries.size(), 4); } test_case(ecs_cache_components) @@ -61,7 +61,7 @@ test_case(ecs_cache_components) expect_different_ptr(cache.get(), nullptr); expect_equal(cache->component_uuids.size(), 1); - expect_equal(cache->tables.size(), 2); + expect_equal(cache->entries.size(), 2); } test_case(ecs_cache_update) @@ -72,23 +72,23 @@ test_case(ecs_cache_update) expect_different_ptr(cache.get(), nullptr); expect_equal(cache->component_uuids.size(), 1); - expect_equal(cache->tables.size(), 0); + expect_equal(cache->entries.size(), 0); container.create(); expect_equal(cache->component_uuids.size(), 1); - expect_equal(cache->tables.size(), 0); + expect_equal(cache->entries.size(), 0); container.create(component_1{10}); expect_equal(cache->component_uuids.size(), 1); - expect_equal(cache->tables.size(), 1); + expect_equal(cache->entries.size(), 1); container.create(component_2{20}); expect_equal(cache->component_uuids.size(), 1); - expect_equal(cache->tables.size(), 1); + expect_equal(cache->entries.size(), 1); container.create(component_1{100}, component_2{200}); expect_equal(cache->component_uuids.size(), 1); - expect_equal(cache->tables.size(), 2); + expect_equal(cache->entries.size(), 2); } test_case(ecs_cache_apply)