New ECS library wip
This commit is contained in:
parent
981629cb74
commit
2667d1aadb
7 changed files with 347 additions and 0 deletions
9
libs/ecs/CMakeLists.txt
Normal file
9
libs/ecs/CMakeLists.txt
Normal 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)
|
||||
|
||||
33
libs/ecs/include/psemek/ecs/component_index.hpp
Normal file
33
libs/ecs/include/psemek/ecs/component_index.hpp
Normal 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_;
|
||||
};
|
||||
|
||||
}
|
||||
10
libs/ecs/include/psemek/ecs/component_mask.hpp
Normal file
10
libs/ecs/include/psemek/ecs/component_mask.hpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/util/dynamic_bitset.hpp>
|
||||
|
||||
namespace psemek::ecs
|
||||
{
|
||||
|
||||
using component_mask = util::dynamic_bitset;
|
||||
|
||||
}
|
||||
53
libs/ecs/include/psemek/ecs/entity_container.hpp
Normal file
53
libs/ecs/include/psemek/ecs/entity_container.hpp
Normal 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
|
||||
};
|
||||
|
||||
}
|
||||
17
libs/ecs/include/psemek/ecs/entity_handle.hpp
Normal file
17
libs/ecs/include/psemek/ecs/entity_handle.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
181
libs/ecs/include/psemek/ecs/table.hpp
Normal file
181
libs/ecs/include/psemek/ecs/table.hpp
Normal 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), ...);
|
||||
}
|
||||
|
||||
}
|
||||
44
libs/ecs/include/psemek/ecs/table_container.hpp
Normal file
44
libs/ecs/include/psemek/ecs/table_container.hpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue