diff --git a/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp new file mode 100644 index 00000000..edd7fd8a --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +namespace psemek::ecs::detail +{ + + template + struct static_apply_helper + { + std::size_t row_count; + entity_id const * entity_id_pointer; + entity_data const * entity_data_pointer; + table::component_pointer pointers[sizeof...(Components)]; + + static_apply_helper(util::span entity_ids, util::span entities) + : row_count(entity_ids.size()) + , entity_id_pointer(entity_ids.data()) + , entity_data_pointer(entities.data()) + {} + + template + void apply(Function && function) + { + apply_impl(function, std::make_index_sequence{}); + } + + template + void apply_impl(Function && function, std::index_sequence) + { + auto id = *entity_id_pointer; + auto epoch = entity_data_pointer[id].epoch; + function(entity_handle{id, epoch}, *reinterpret_cast(pointers[I].data) ...); + } + + std::size_t size() const + { + return row_count; + } + + void advance() + { + ++entity_id_pointer; + + std::size_t i = 0; + ((pointers[i++].data += stride()), ...); + } + }; + +} diff --git a/libs/ecs/include/psemek/ecs/component_index.hpp b/libs/ecs/include/psemek/ecs/detail/component_index.hpp similarity index 89% rename from libs/ecs/include/psemek/ecs/component_index.hpp rename to libs/ecs/include/psemek/ecs/detail/component_index.hpp index 0ff92277..7a8fe204 100644 --- a/libs/ecs/include/psemek/ecs/component_index.hpp +++ b/libs/ecs/include/psemek/ecs/detail/component_index.hpp @@ -1,11 +1,11 @@ #pragma once -#include +#include #include #include #include -namespace psemek::ecs +namespace psemek::ecs::detail { struct component_index diff --git a/libs/ecs/include/psemek/ecs/component_mask.hpp b/libs/ecs/include/psemek/ecs/detail/component_mask.hpp similarity index 78% rename from libs/ecs/include/psemek/ecs/component_mask.hpp rename to libs/ecs/include/psemek/ecs/detail/component_mask.hpp index c767c97d..8a7dda46 100644 --- a/libs/ecs/include/psemek/ecs/component_mask.hpp +++ b/libs/ecs/include/psemek/ecs/detail/component_mask.hpp @@ -2,7 +2,7 @@ #include -namespace psemek::ecs +namespace psemek::ecs::detail { using component_mask = util::dynamic_bitset; diff --git a/libs/ecs/include/psemek/ecs/detail/entity_list.hpp b/libs/ecs/include/psemek/ecs/detail/entity_list.hpp new file mode 100644 index 00000000..082cd171 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/entity_list.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::ecs::detail +{ + + struct table; + + struct entity_data + { + struct table * table = nullptr; + std::uint32_t row = 0; + entity_epoch epoch = 0; + }; + + struct entity_list + { + entity_id create(table * table, std::uint32_t row); + void destroy(entity_id id); + + util::span get_entities() const + { + return entities_; + } + + util::span get_entities() + { + return entities_; + } + + private: + std::vector entities_; + std::vector free_ids_; + + void allocate_ids(); + }; + +} diff --git a/libs/ecs/include/psemek/ecs/detail/stride.hpp b/libs/ecs/include/psemek/ecs/detail/stride.hpp new file mode 100644 index 00000000..a088c581 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/stride.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace psemek::ecs::detail +{ + + template + constexpr bool is_empty_v = std::is_empty_v; + + template + constexpr std::size_t stride() + { + return is_empty_v ? 0 : sizeof(T); + } + +} diff --git a/libs/ecs/include/psemek/ecs/detail/table.hpp b/libs/ecs/include/psemek/ecs/detail/table.hpp new file mode 100644 index 00000000..cea85fbb --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/table.hpp @@ -0,0 +1,203 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace psemek::ecs::detail +{ + + struct table + { + struct component_pointer + { + std::uint8_t * data = nullptr; + }; + + util::span get_component_uuids() const + { + return component_uuids_; + } + + util::span get_component_pointers() const + { + return component_pointers_; + } + + std::size_t component_count() const + { + return component_uuids_.size(); + } + + std::size_t row_count() const + { + return row_count_; + } + + virtual std::size_t push_row(entity_id id) = 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 + { + return entity_ids_; + } + + virtual ~table() = default; + + protected: + std::vector component_uuids_; + std::vector component_pointers_; + std::size_t row_count_ = 0; + + std::vector entity_ids_; + }; + + template + struct table_impl + : table + { + table_impl(util::span component_uuids); + + std::size_t push_row(entity_id id) override; + std::size_t push_row_with_components(entity_id id, Components && ... components); + void swap_rows(std::size_t row1, std::size_t row2) override; + void pop_row() override; + + private: + std::size_t capacity_ = 0; + + void reallocate(); + }; + + template + table_impl::table_impl(util::span component_uuids) + { + assert(sizeof...(Components) == component_uuids.size()); + component_uuids_.assign(component_uuids.begin(), component_uuids.end()); + component_pointers_.resize(sizeof...(Components)); + } + + template + std::size_t table_impl::push_row(entity_id id) + { + 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_ids_.push_back(id); + + return row_count_ - 1; + } + + template + std::size_t table_impl::push_row_with_components(entity_id id, Components && ... components) + { + if (row_count_ == capacity_) + reallocate(); + + [[maybe_unused]] auto push_row_impl = [&](std::uint8_t * data, Component && value) + { + if constexpr (!detail::is_empty_v) + { + new (reinterpret_cast(data) + row_count_) Component{std::move(value)}; + } + }; + + std::size_t i = 0; + (push_row_impl.template operator()(component_pointers_[i++].data, std::move(components)), ...); + ++row_count_; + + entity_ids_.push_back(id); + + 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_ids_[row1], entity_ids_[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_ids_.pop_back(); + } + + 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), ...); + } + +} diff --git a/libs/ecs/include/psemek/ecs/table_container.hpp b/libs/ecs/include/psemek/ecs/detail/table_container.hpp similarity index 75% rename from libs/ecs/include/psemek/ecs/table_container.hpp rename to libs/ecs/include/psemek/ecs/detail/table_container.hpp index 3edd6c29..f7d5cc2c 100644 --- a/libs/ecs/include/psemek/ecs/table_container.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table_container.hpp @@ -1,11 +1,11 @@ #pragma once -#include -#include +#include +#include #include -namespace psemek::ecs +namespace psemek::ecs::detail { // TODO: store tables in a bitmask trie balanced by subtree size @@ -13,7 +13,7 @@ namespace psemek::ecs struct table_container { template - table_impl & insert(component_mask const & mask); + table_impl & insert(component_mask const & mask, util::span component_uuids); template void apply(Function && function, component_mask const & mask); @@ -23,11 +23,11 @@ namespace psemek::ecs }; template - table_impl & table_container::insert(component_mask const & mask) + table_impl & table_container::insert(component_mask const & mask, util::span component_uuids) { auto & result = tables_[mask]; if (!result) - result = std::make_unique>(); + result = std::make_unique>(component_uuids); return *static_cast *>(result.get()); } diff --git a/libs/ecs/include/psemek/ecs/entity_accessor.hpp b/libs/ecs/include/psemek/ecs/entity_accessor.hpp new file mode 100644 index 00000000..d736ab54 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/entity_accessor.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace psemek::ecs +{ + + struct entity_accessor + { + + + private: + + }; + +} diff --git a/libs/ecs/include/psemek/ecs/entity_container.hpp b/libs/ecs/include/psemek/ecs/entity_container.hpp index ce4fa88c..8dd12165 100644 --- a/libs/ecs/include/psemek/ecs/entity_container.hpp +++ b/libs/ecs/include/psemek/ecs/entity_container.hpp @@ -1,9 +1,10 @@ #pragma once -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace psemek::ecs @@ -11,43 +12,99 @@ namespace psemek::ecs struct entity_container { + entity_handle create() + { + // Specialization for an empty entity to + // prevent creation of an empty uuid array + + detail::component_mask mask; + + auto & table = table_container_.insert<>(mask, {}); + + auto id = entity_list_.create(&table, table.row_count()); + table.push_row(id); + + return {id, 0}; + } + template entity_handle create(Components && ... components) { - component_mask mask = component_index_.make_component_mask(components.uuid()...); - // TODO - return {}; + util::uuid component_uuids[] { components.uuid()... }; + detail::component_mask mask = component_index_.make_component_mask(util::span(component_uuids)); + + auto & table = table_container_.insert(mask, component_uuids); + + auto id = entity_list_.create(&table, table.row_count()); + table.push_row_with_components(id, std::move(components)...); + + return {id, entity_list_.get_entities()[id].epoch}; } - bool alive(entity_handle const & entity) const; - - void destroy(entity_handle const & entity); - - template - void apply(Function && function, util::span component_uuids) + bool alive(entity_handle const & entity) const { - component_mask mask = component_index_.make_component_mask(component_uuids); - table_container_.apply([&](table & table){ - // TODO: extract specific component pointers and apply the function - // to each element, using stride to advance pointers - // TODO: maybe store UUIDS or component indices in the table to simplify - // extracting the component pointers - }, mask); + return entity_list_.get_entities()[entity.id].epoch == entity.epoch; + } + + void destroy(entity_handle const & entity) + { + // 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); + data.table->pop_row(); + auto swap_id = table_entity_ids[data.row]; + auto & swap_data = entities[swap_id]; + swap_data.row = data.row; + entity_list_.destroy(entity.id); } template void apply(Function && function) { - util::uuid component_uuids[] { Components::uuid() ... }; - // TODO: call function, casting the raw uint8_t component pointers - // to actual component types + util::uuid const component_uuids[] { Components::uuid() ... }; + + detail::component_mask mask = component_index_.make_component_mask(util::span(component_uuids)); + + table_container_.apply([&](detail::table & table){ + detail::static_apply_helper apply_helper(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 (component_uuids[i] == table.get_component_uuids()[j]) + apply_helper.pointers[i++] = table.get_component_pointers()[j]; + + for (std::size_t i = 0; i < apply_helper.size(); ++i) + { + apply_helper.apply(function); + apply_helper.advance(); + } + + }, mask); + } + + template + Component & get(entity_handle const & entity) + { + util::uuid const uuid = Component::uuid(); + + auto const & data = entity_list_.get_entities()[entity.id]; + + auto const component_uuids = data.table->get_component_uuids(); + + for (std::size_t i = 0; i < component_uuids.size(); ++i) + if (uuid == component_uuids[i]) + return *reinterpret_cast(data.table->get_component_pointers()[i].data + detail::stride() * data.row); + + assert(false); + __builtin_unreachable(); } private: - mutable component_index component_index_; - table_container table_container_; - // TODO: store entity epochs - // TODO: store entity id -> table * mapping + detail::entity_list entity_list_; + mutable detail::component_index component_index_; + detail::table_container table_container_; }; } diff --git a/libs/ecs/include/psemek/ecs/entity_handle.hpp b/libs/ecs/include/psemek/ecs/entity_handle.hpp index d9170e40..ffe0aa5e 100644 --- a/libs/ecs/include/psemek/ecs/entity_handle.hpp +++ b/libs/ecs/include/psemek/ecs/entity_handle.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace psemek::ecs { @@ -14,4 +15,10 @@ namespace psemek::ecs entity_epoch epoch; }; + inline std::ostream & operator << (std::ostream & out, entity_handle const & handle) + { + out << '(' << handle.id << ',' << handle.epoch << ')'; + return out; + } + } diff --git a/libs/ecs/include/psemek/ecs/table.hpp b/libs/ecs/include/psemek/ecs/table.hpp deleted file mode 100644 index f4d33a87..00000000 --- a/libs/ecs/include/psemek/ecs/table.hpp +++ /dev/null @@ -1,181 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -namespace psemek::ecs -{ - - struct table - { - struct component_pointer - { - std::uint8_t * data = nullptr; - std::size_t stride = 0; - }; - - component_pointer get_component(std::size_t i) - { - return component_pointers_[i]; - } - - std::size_t component_count() const - { - return component_count_; - } - - std::size_t size() const - { - return size_; - } - - virtual std::size_t push_row(entity_id id) = 0; - virtual void swap_rows(std::size_t row1, std::size_t row2) = 0; - virtual void pop_row() = 0; - - entity_id row_entity_id(std::size_t row) const - { - return entity_ids_[row]; - } - - virtual ~table() = default; - - protected: - component_pointer const * component_pointers_ = nullptr; - std::size_t component_count_ = 0; - std::size_t size_ = 0; - - std::vector entity_ids_; - }; - - template - struct table_impl - : table - { - table_impl(); - - std::size_t push_row(entity_id id) override; - void swap_rows(std::size_t row1, std::size_t row2) override; - void pop_row() override; - - private: - component_pointer component_pointers_storage_[sizeof...(Components)]; - std::size_t capacity_ = 0; - - void init_components_stride(); - void reallocate(); - }; - - template - table_impl::table_impl() - { - component_pointers_ = component_pointers_storage_; - component_count_ = sizeof...(Components); - init_components_stride(); - } - - template - std::size_t table_impl::push_row(entity_id id) - { - if (size_ == capacity_) - reallocate(); - - auto push_row_impl = [&](std::uint8_t * data) - { - if constexpr (!std::is_empty_v) - { - new (reinterpret_cast(data) + size_) Component{}; - } - }; - - std::size_t i = 0; - (push_row_impl.template operator()(component_pointers_storage_[i++].data), ...); - ++size_; - - entity_ids_.push_back(id); - - return size_ - 1; - } - - template - void table_impl::swap_rows(std::size_t row1, std::size_t row2) - { - auto swap_rows_impl = [&](std::uint8_t * data) - { - if constexpr (!std::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_storage_[i++].data), ...); - - std::swap(entity_ids_[row1], entity_ids_[row2]); - } - - template - void table_impl::pop_row() - { - auto row = size_ - 1; - - auto pop_row_impl = [&](std::uint8_t * data) - { - if constexpr (!std::is_empty_v) - { - (reinterpret_cast(data) + size_)->~Component(); - } - }; - - std::size_t i = 0; - (pop_row_impl.template operator()(component_pointers_storage_[i++].data), ...); - entity_ids_.pop_back(); - --size_; - } - - template - void table_impl::init_components_stride() - { - std::size_t i = 0; - ((component_pointers_storage_[i].stride = std::is_empty_v ? 0 : sizeof(Components), ++i), ...); - } - - template - void table_impl::reallocate() - { - std::size_t const new_capacity = capacity_ == 0 ? 64 : capacity_ * 2; - - auto reallocate_impl = [&](std::uint8_t * & data) - { - if constexpr (std::is_empty_v) - { - if (!data) - data = reinterpret_cast(new Component[1]); - } - else - { - auto new_data = new std::uint8_t[new_capacity * sizeof(Component)]; - - auto old_begin = reinterpret_cast(data); - auto old_end = old_begin + size_; - 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_storage_[i++].data), ...); - } - -} diff --git a/libs/ecs/source/detail/entity_list.cpp b/libs/ecs/source/detail/entity_list.cpp new file mode 100644 index 00000000..25b4bf2f --- /dev/null +++ b/libs/ecs/source/detail/entity_list.cpp @@ -0,0 +1,36 @@ +#include + +namespace psemek::ecs::detail +{ + + entity_id entity_list::create(table * table, std::uint32_t row) + { + if (free_ids_.empty()) + allocate_ids(); + + auto id = free_ids_.back(); + free_ids_.pop_back(); + + entities_[id].table = table; + entities_[id].row = row; + + return id; + } + + void entity_list::destroy(entity_id id) + { + entities_[id].epoch += 1; + free_ids_.push_back(id); + } + + void entity_list::allocate_ids() + { + static constexpr std::size_t batch_size = 1024; + + auto old_size = entities_.size(); + entities_.resize(entities_.size() + batch_size); + for (std::size_t id = entities_.size(); id --> old_size;) + free_ids_.push_back(id); + } + +}