New ECS library wip

This commit is contained in:
Nikita Lisitsa 2023-08-19 15:21:17 +03:00
parent 981629cb74
commit 2667d1aadb
7 changed files with 347 additions and 0 deletions

9
libs/ecs/CMakeLists.txt Normal file
View file

@ -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)

View file

@ -0,0 +1,33 @@
#pragma once
#include <psemek/ecs/component_mask.hpp>
#include <psemek/util/uuid.hpp>
#include <psemek/util/span.hpp>
#include <psemek/util/unique_sequential_storage.hpp>
namespace psemek::ecs
{
struct component_index
{
template <typename ... UUIDS>
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<util::uuid const> uuids)
{
component_mask result;
for (util::uuid const & uuid : uuids)
result.set(storage_.insert(uuid), true);
return result;
}
private:
util::unique_sequential_storage<util::uuid> storage_;
};
}

View file

@ -0,0 +1,10 @@
#pragma once
#include <psemek/util/dynamic_bitset.hpp>
namespace psemek::ecs
{
using component_mask = util::dynamic_bitset;
}

View file

@ -0,0 +1,53 @@
#pragma once
#include <psemek/ecs/component_index.hpp>
#include <psemek/ecs/component_mask.hpp>
#include <psemek/ecs/entity_handle.hpp>
#include <psemek/ecs/table_container.hpp>
#include <psemek/util/span.hpp>
namespace psemek::ecs
{
struct entity_container
{
template <typename ... Components>
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 <typename Function>
void apply(Function && function, util::span<util::uuid const> 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 <typename ... Components, typename Function>
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
};
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <cstdint>
namespace psemek::ecs
{
using entity_id = std::uint32_t;
using entity_epoch = std::uint32_t;
struct entity_handle
{
entity_id id;
entity_epoch epoch;
};
}

View file

@ -0,0 +1,181 @@
#pragma once
#include <psemek/ecs/entity_handle.hpp>
#include <memory>
#include <type_traits>
#include <vector>
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_id> entity_ids_;
};
template <typename ... Components>
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 <typename ... Components>
table_impl<Components...>::table_impl()
{
component_pointers_ = component_pointers_storage_;
component_count_ = sizeof...(Components);
init_components_stride();
}
template <typename ... Components>
std::size_t table_impl<Components...>::push_row(entity_id id)
{
if (size_ == capacity_)
reallocate();
auto push_row_impl = [&]<typename Component>(std::uint8_t * data)
{
if constexpr (!std::is_empty_v<Component>)
{
new (reinterpret_cast<Component *>(data) + size_) Component{};
}
};
std::size_t i = 0;
(push_row_impl.template operator()<Components>(component_pointers_storage_[i++].data), ...);
++size_;
entity_ids_.push_back(id);
return size_ - 1;
}
template <typename ... Components>
void table_impl<Components...>::swap_rows(std::size_t row1, std::size_t row2)
{
auto swap_rows_impl = [&]<typename Component>(std::uint8_t * data)
{
if constexpr (!std::is_empty_v<Component>)
{
auto cdata = reinterpret_cast<Component *>(data);
std::iter_swap(cdata + row1, cdata + row2);
}
};
std::size_t i = 0;
(swap_rows_impl.template operator()<Components>(component_pointers_storage_[i++].data), ...);
std::swap(entity_ids_[row1], entity_ids_[row2]);
}
template <typename ... Components>
void table_impl<Components...>::pop_row()
{
auto row = size_ - 1;
auto pop_row_impl = [&]<typename Component>(std::uint8_t * data)
{
if constexpr (!std::is_empty_v<Component>)
{
(reinterpret_cast<Component *>(data) + size_)->~Component();
}
};
std::size_t i = 0;
(pop_row_impl.template operator()<Components>(component_pointers_storage_[i++].data), ...);
entity_ids_.pop_back();
--size_;
}
template <typename ... Components>
void table_impl<Components...>::init_components_stride()
{
std::size_t i = 0;
((component_pointers_storage_[i].stride = std::is_empty_v<Components> ? 0 : sizeof(Components), ++i), ...);
}
template <typename ... Components>
void table_impl<Components...>::reallocate()
{
std::size_t const new_capacity = capacity_ == 0 ? 64 : capacity_ * 2;
auto reallocate_impl = [&]<typename Component>(std::uint8_t * & data)
{
if constexpr (std::is_empty_v<Component>)
{
if (!data)
data = reinterpret_cast<std::uint8_t *>(new Component[1]);
}
else
{
auto new_data = new std::uint8_t[new_capacity * sizeof(Component)];
auto old_begin = reinterpret_cast<Component *>(data);
auto old_end = old_begin + size_;
auto new_begin = reinterpret_cast<Component *>(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()<Components>(component_pointers_storage_[i++].data), ...);
}
}

View file

@ -0,0 +1,44 @@
#pragma once
#include <psemek/ecs/component_mask.hpp>
#include <psemek/ecs/table.hpp>
#include <vector>
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 <typename ... Components>
table_impl<Components...> & insert(component_mask const & mask);
template <typename Function>
void apply(Function && function, component_mask const & mask);
private:
std::unordered_map<component_mask, std::unique_ptr<table>> tables_;
};
template <typename ... Components>
table_impl<Components...> & table_container::insert(component_mask const & mask)
{
auto & result = tables_[mask];
if (!result)
result = std::make_unique<table_impl<Components...>>();
return *static_cast<table_impl<Components...> *>(result.get());
}
template <typename Function>
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);
}
}
}