diff --git a/libs/ecs/CMakeLists.txt b/libs/ecs/CMakeLists.txt new file mode 100644 index 00000000..2feb2512 --- /dev/null +++ b/libs/ecs/CMakeLists.txt @@ -0,0 +1,9 @@ +file(GLOB_RECURSE PSEMEK_ECS_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp") +file(GLOB_RECURSE PSEMEK_ECS_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp") + +psemek_add_library(psemek-ecs ${PSEMEK_ECS_HEADERS} ${PSEMEK_ECS_SOURCES}) +target_include_directories(psemek-ecs PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(psemek-ecs PUBLIC psemek-util) + +#psemek_glob_tests(psemek-ecs tests) + diff --git a/libs/ecs/include/psemek/ecs/component_index.hpp b/libs/ecs/include/psemek/ecs/component_index.hpp new file mode 100644 index 00000000..0ff92277 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/component_index.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include + +namespace psemek::ecs +{ + + struct component_index + { + template + component_mask make_component_mask(UUIDS const & ... uuids) + { + component_mask result; + (result.set(storage_.insert(uuids), true), ...); + return result; + } + + component_mask make_component_mask(util::span uuids) + { + component_mask result; + for (util::uuid const & uuid : uuids) + result.set(storage_.insert(uuid), true); + return result; + } + + private: + util::unique_sequential_storage storage_; + }; + +} diff --git a/libs/ecs/include/psemek/ecs/component_mask.hpp b/libs/ecs/include/psemek/ecs/component_mask.hpp new file mode 100644 index 00000000..c767c97d --- /dev/null +++ b/libs/ecs/include/psemek/ecs/component_mask.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace psemek::ecs +{ + + using component_mask = util::dynamic_bitset; + +} diff --git a/libs/ecs/include/psemek/ecs/entity_container.hpp b/libs/ecs/include/psemek/ecs/entity_container.hpp new file mode 100644 index 00000000..ce4fa88c --- /dev/null +++ b/libs/ecs/include/psemek/ecs/entity_container.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace psemek::ecs +{ + + struct entity_container + { + template + entity_handle create(Components && ... components) + { + component_mask mask = component_index_.make_component_mask(components.uuid()...); + // TODO + return {}; + } + + bool alive(entity_handle const & entity) const; + + void destroy(entity_handle const & entity); + + template + void apply(Function && function, util::span component_uuids) + { + 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); + } + + 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 + } + + private: + mutable component_index component_index_; + table_container table_container_; + // TODO: store entity epochs + // TODO: store entity id -> table * mapping + }; + +} diff --git a/libs/ecs/include/psemek/ecs/entity_handle.hpp b/libs/ecs/include/psemek/ecs/entity_handle.hpp new file mode 100644 index 00000000..d9170e40 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/entity_handle.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace psemek::ecs +{ + + using entity_id = std::uint32_t; + using entity_epoch = std::uint32_t; + + struct entity_handle + { + entity_id id; + entity_epoch epoch; + }; + +} diff --git a/libs/ecs/include/psemek/ecs/table.hpp b/libs/ecs/include/psemek/ecs/table.hpp new file mode 100644 index 00000000..f4d33a87 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/table.hpp @@ -0,0 +1,181 @@ +#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/include/psemek/ecs/table_container.hpp b/libs/ecs/include/psemek/ecs/table_container.hpp new file mode 100644 index 00000000..3edd6c29 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/table_container.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::ecs +{ + + // TODO: store tables in a bitmask trie balanced by subtree size + // TODO: support explicit or implicit query cache + struct table_container + { + template + table_impl & insert(component_mask const & mask); + + template + void apply(Function && function, component_mask const & mask); + + private: + std::unordered_map> tables_; + }; + + template + table_impl & table_container::insert(component_mask const & mask) + { + auto & result = tables_[mask]; + if (!result) + result = std::make_unique>(); + return *static_cast *>(result.get()); + } + + template + void table_container::apply(Function && function, component_mask const & mask) + { + for (auto & table : tables_) + { + if (!util::is_subset(mask, table.first)) continue; + function(*table.second); + } + } + +}