ECS constructors wip

This commit is contained in:
Nikita Lisitsa 2023-12-18 12:38:58 +03:00
parent 0b1522722c
commit 028b4e1296
10 changed files with 192 additions and 30 deletions

View file

@ -19,7 +19,6 @@ namespace psemek::ecs
{ {
using query_cache = std::shared_ptr<detail::query_cache>; using query_cache = std::shared_ptr<detail::query_cache>;
using token = std::shared_ptr<void>;
// TODO: // TODO:
// - Fully document which functions can be called from which callbacks // - Fully document which functions can be called from which callbacks
@ -283,12 +282,11 @@ namespace psemek::ecs
* When attaching components to an entity, the constructor is called * When attaching components to an entity, the constructor is called
* exactly when the entity didn't match the constructor's component types * exactly when the entity didn't match the constructor's component types
* before attaching new components, and does match them after attaching. * before attaching new components, and does match them after attaching.
* TODO: implement this behavior
* *
* The constructor function must have the same signature as a function * The constructor function must have the same signature as a function
* passed to the `apply<Components...>()` call. * passed to the `apply<Components...>()` call.
* *
* The constructor can immediately destroy the created entity.
*
* The constuctor is not considered to be a modification of the entity, i.e. it * The constuctor is not considered to be a modification of the entity, i.e. it
* doesn't trigger modification callbacks. * doesn't trigger modification callbacks.
* *
@ -298,13 +296,11 @@ namespace psemek::ecs
* @warning If any two of the passed component types are equal, the call fails with * @warning If any two of the passed component types are equal, the call fails with
* a compilation error * a compilation error
* @warning If the constructor modifies the entity's archetype (i.e. attaches or * @warning If the constructor modifies the entity's archetype (i.e. attaches or
* detaches components), it might be called recursively, leading to * detaches components), the behavior is undefined
* infinite recursion. It is best not to change the archetype from the * @warning If the constructor destroys the entity, the behavior is undefined
* constructor.
*/ */
// TODO: implement
template <typename ... Components, typename Function> template <typename ... Components, typename Function>
token constructor(Function && function); void constructor(Function && function);
/** Register a destructor. Each time an entity is destroyed that has /** Register a destructor. Each time an entity is destroyed that has
* the specified set of components, the destructor is called for * the specified set of components, the destructor is called for
@ -322,6 +318,7 @@ namespace psemek::ecs
* When detaching components from an entity, the destructor is called * When detaching components from an entity, the destructor is called
* exactly when the entity did match the destructor's component types * exactly when the entity did match the destructor's component types
* before detaching components, and doesn't match them after detaching. * before detaching components, and doesn't match them after detaching.
* TODO: implement this behavior
* *
* The destructor function must have the same signature as a function * The destructor function must have the same signature as a function
* passed to the `apply<Components...>()` call. * passed to the `apply<Components...>()` call.
@ -337,10 +334,11 @@ namespace psemek::ecs
* a compilation error * a compilation error
* @warning If the destructor modifies the entity's archetype (i.e. attaches or * @warning If the destructor modifies the entity's archetype (i.e. attaches or
* detaches components), the behavior is undefined * detaches components), the behavior is undefined
* @warning If the destructor destroys the entity recursively, the behavior is undefined
*/ */
// TODO: implement // TODO: implement
template <typename ... Components, typename Function> template <typename ... Components, typename Function>
token destructor(Function && function); void destructor(Function && function);
/** Register a component modification callback. Each time an entity that has /** Register a component modification callback. Each time an entity that has
* the specified set of components is modified, and the modification affects * the specified set of components is modified, and the modification affects
@ -376,7 +374,7 @@ namespace psemek::ecs
*/ */
// TODO: implement // TODO: implement
template <typename ... Components, typename Function> template <typename ... Components, typename Function>
token watch(Function && function); void watch(Function && function);
private: private:
detail::entity_list entity_list_; detail::entity_list entity_list_;
@ -387,6 +385,8 @@ namespace psemek::ecs
std::vector<util::uuid> uuid_helper_; std::vector<util::uuid> uuid_helper_;
util::hash_set<util::uuid> uuid_set_helper_; util::hash_set<util::uuid> uuid_set_helper_;
std::vector<query_cache> callback_caches_;
detail::table * insert_table(std::vector<std::unique_ptr<detail::column>> columns); detail::table * insert_table(std::vector<std::unique_ptr<detail::column>> columns);
void do_destroy(handle const & entity); void do_destroy(handle const & entity);
void remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> entities); void remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> entities);
@ -420,13 +420,16 @@ namespace psemek::ecs
if (table->get_iteration_data()) if (table->get_iteration_data())
table = table->get_delayed_table(); table = table->get_delayed_table();
auto id = entity_list_.create(table, table->row_count()); auto row = table->row_count();
auto id = entity_list_.create(table, row);
handle handle{id, entity_list_.get_entities()[id].epoch}; handle handle{id, entity_list_.get_entities()[id].epoch};
[[maybe_unused]] accessor accessor = get(handle); [[maybe_unused]] accessor accessor = get(handle);
table->push_row(handle); table->push_row(handle);
((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...); ((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);
table->trigger_constructors(*this, row);
return handle; return handle;
} }
@ -477,9 +480,9 @@ namespace psemek::ecs
template <typename ... Components> template <typename ... Components>
void container::detach(handle const & entity) void container::detach(handle const & entity)
{ {
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different"); static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
(uuid_set_helper_.insert(std::remove_cvref_t<Components>::uuid()), ...); (uuid_set_helper_.insert(std::remove_const_t<Components>::uuid()), ...);
auto & data = entity_list_.get_entities()[entity.id]; auto & data = entity_list_.get_entities()[entity.id];
for (auto const & column : data.table->columns()) for (auto const & column : data.table->columns())
@ -534,7 +537,7 @@ namespace psemek::ecs
{ {
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different"); static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<std::remove_cvref_t<Components>...>, Function>::type; using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components...>, Function>::type;
static_assert(invocable_type::value, "function is not invocable with these components"); static_assert(invocable_type::value, "function is not invocable with these components");
@ -582,7 +585,7 @@ namespace psemek::ecs
{ {
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different"); static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
using invocable_type = typename detail::filter_with<detail::batch_invocable, std::tuple<std::remove_cvref_t<Components>...>, Function>::type; using invocable_type = typename detail::filter_with<detail::batch_invocable, std::tuple<Components...>, Function>::type;
static_assert(invocable_type::value, "function is not batch-invocable with these components"); static_assert(invocable_type::value, "function is not batch-invocable with these components");
@ -602,4 +605,30 @@ namespace psemek::ecs
return cache; return cache;
} }
template <typename ... Components, typename Function>
void container::constructor(Function && function)
{
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components ...>, Function>::type;
static_assert(invocable_type::value, "function is not invocable with these components");
query_cache cache = this->cache<Components...>();
cache->constructor_factory = [function = std::move(function)](std::vector<std::uint32_t> const & column_indices) -> detail::table_callback {
return [function, column_indices](container & container, detail::table & table, std::uint32_t row){
typename detail::filter_with<detail::static_apply_helper, std::tuple<Components ...>>::type apply_helper(container, table.entity_handles());
for (std::size_t i = 0; i < apply_helper.column_count; ++i)
apply_helper.pointers[i] = table.columns()[column_indices[i]]->data();
apply_helper.advance(row);
apply_helper.apply(function);
};
};
callback_caches_.push_back(cache);
}
} }

View file

@ -84,6 +84,8 @@ namespace psemek::ecs::detail
template <typename ... Components> template <typename ... Components>
struct static_apply_helper struct static_apply_helper
{ {
static constexpr std::size_t column_count = sizeof...(Components);
container & parent; container & parent;
std::size_t row_count; std::size_t row_count;
handle const * entity_handles_pointer; handle const * entity_handles_pointer;
@ -132,6 +134,14 @@ namespace psemek::ecs::detail
std::size_t i = 0; std::size_t i = 0;
((pointers[i++] += stride<Components>()), ...); ((pointers[i++] += stride<Components>()), ...);
} }
void advance(std::size_t rows)
{
entity_handles_pointer += rows;
std::size_t i = 0;
((pointers[i++] += stride<Components>() * rows), ...);
}
}; };
} }

View file

@ -0,0 +1,22 @@
#pragma once
#include <psemek/ecs/handle.hpp>
#include <psemek/util/function.hpp>
#include <memory>
namespace psemek::ecs
{
struct container;
namespace detail
{
struct table;
using table_callback = util::function<void(container &, table &, std::uint32_t)>;
}
}

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <psemek/ecs/detail/callback.hpp>
#include <psemek/util/uuid.hpp> #include <psemek/util/uuid.hpp>
#include <vector> #include <vector>
@ -13,7 +14,7 @@ namespace psemek::ecs::detail
struct query_cache_entry struct query_cache_entry
{ {
struct table * table = nullptr; struct table * table = nullptr;
std::vector<column *> columns; std::vector<std::uint32_t> columns_indices;
}; };
struct query_cache struct query_cache
@ -22,6 +23,8 @@ namespace psemek::ecs::detail
std::vector<util::uuid> without_uuids; std::vector<util::uuid> without_uuids;
std::vector<query_cache_entry> entries; std::vector<query_cache_entry> entries;
util::function<table_callback(std::vector<std::uint32_t> const &)> constructor_factory;
void add(table * table); void add(table * table);
}; };

View file

@ -3,6 +3,7 @@
#include <psemek/ecs/handle.hpp> #include <psemek/ecs/handle.hpp>
#include <psemek/ecs/detail/component_hash.hpp> #include <psemek/ecs/detail/component_hash.hpp>
#include <psemek/ecs/detail/column.hpp> #include <psemek/ecs/detail/column.hpp>
#include <psemek/ecs/detail/callback.hpp>
#include <psemek/util/uuid.hpp> #include <psemek/util/uuid.hpp>
#include <psemek/util/span.hpp> #include <psemek/util/span.hpp>
#include <psemek/util/hash_table.hpp> #include <psemek/util/hash_table.hpp>
@ -26,7 +27,8 @@ namespace psemek::ecs::detail
return hash_; return hash_;
} }
detail::column * column(util::uuid const & uuid) const; std::optional<std::size_t> column_index(util::uuid const & uuid) const;
struct column * column(util::uuid const & uuid) const;
util::span<handle const> entity_handles() const util::span<handle const> entity_handles() const
{ {
@ -38,11 +40,6 @@ namespace psemek::ecs::detail
return columns_; return columns_;
} }
std::size_t column_count() const
{
return component_uuid_to_column_.size();
}
std::size_t row_count() const std::size_t row_count() const
{ {
return entity_handles_.size(); return entity_handles_.size();
@ -75,10 +72,14 @@ namespace psemek::ecs::detail
std::vector<std::type_index> const & non_copyable_components() const; std::vector<std::type_index> const & non_copyable_components() const;
void add_constructor(table_callback callback);
void trigger_constructors(container & container, std::uint32_t row);
protected: protected:
std::size_t hash_; std::size_t hash_;
std::vector<std::unique_ptr<detail::column>> columns_; std::vector<std::unique_ptr<detail::column>> columns_;
util::hash_map<util::uuid, detail::column *> component_uuid_to_column_; util::hash_map<util::uuid, std::uint32_t> component_uuid_to_column_index_;
std::vector<handle> entity_handles_; std::vector<handle> entity_handles_;
@ -87,6 +88,8 @@ namespace psemek::ecs::detail
std::unique_ptr<table> delayed_table_; std::unique_ptr<table> delayed_table_;
std::vector<std::type_index> non_copyable_components_; std::vector<std::type_index> non_copyable_components_;
std::shared_ptr<std::vector<table_callback>> constructors_;
}; };
} }

View file

@ -26,7 +26,7 @@ namespace psemek::ecs::detail
{ {
bool operator()(util::span<util::uuid const> const & uuids, std::unique_ptr<table> const & table) const bool operator()(util::span<util::uuid const> const & uuids, std::unique_ptr<table> const & table) const
{ {
if (uuids.size() != table->column_count()) if (uuids.size() != table->columns().size())
return false; return false;
for (auto const & uuid : uuids) for (auto const & uuid : uuids)
if (!table->column(uuid)) if (!table->column(uuid))

View file

@ -28,6 +28,12 @@ namespace psemek::ecs::detail
using type = typename filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, RemainingComponents...>::type; using type = typename filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, RemainingComponents...>::type;
}; };
template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents, typename Component, typename ... RemainingComponents>
struct filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, ecs::without<Component> const, RemainingComponents...>
{
using type = typename filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, RemainingComponents...>::type;
};
template <template <typename ...> typename MetaFunction, typename ExtraArgsTuple, typename FilteredComponentsTuple, typename ... Components> template <template <typename ...> typename MetaFunction, typename ExtraArgsTuple, typename FilteredComponentsTuple, typename ... Components>
struct filter_without_impl; struct filter_without_impl;
@ -49,6 +55,12 @@ namespace psemek::ecs::detail
using type = typename filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents..., Component>, RemainingComponents...>::type; using type = typename filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents..., Component>, RemainingComponents...>::type;
}; };
template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents, typename Component, typename ... RemainingComponents>
struct filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, const ecs::without<Component>, RemainingComponents...>
{
using type = typename filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents..., Component>, RemainingComponents...>::type;
};
template <template <typename ...> typename MetaFunction, typename ComponentsTuple, typename ... ExtraArgs> template <template <typename ...> typename MetaFunction, typename ComponentsTuple, typename ... ExtraArgs>
struct filter_with; struct filter_with;

View file

@ -10,7 +10,10 @@ namespace psemek::ecs::detail
entry.table = table; entry.table = table;
for (auto const & uuid : with_uuids) for (auto const & uuid : with_uuids)
entry.columns.push_back(table->column(uuid)); entry.columns_indices.push_back(*table->column_index(uuid));
if (constructor_factory)
table->add_constructor(constructor_factory(entry.columns_indices));
} }
} }

View file

@ -7,23 +7,33 @@ namespace psemek::ecs::detail
table::table(std::vector<std::unique_ptr<detail::column>> columns) table::table(std::vector<std::unique_ptr<detail::column>> columns)
{ {
component_hasher<true> hasher; component_hasher<true> hasher;
for (auto & column : columns) for (std::size_t i = 0; i < columns.size(); ++i)
{ {
auto const & column = columns[i];
auto uuid = column->uuid(); auto uuid = column->uuid();
hasher(uuid); hasher(uuid);
component_uuid_to_column_.insert({uuid, column.get()}); component_uuid_to_column_index_.insert({uuid, i});
if (!column->copy_constructible()) if (!column->copy_constructible())
non_copyable_components_.push_back(column->type()); non_copyable_components_.push_back(column->type());
} }
hash_ = hasher.result; hash_ = hasher.result;
columns_ = std::move(columns); columns_ = std::move(columns);
constructors_ = std::make_shared<std::vector<table_callback>>();
} }
detail::column * table::column(util::uuid const & uuid) const std::optional<std::size_t> table::column_index(util::uuid const & uuid) const
{ {
if (auto it = component_uuid_to_column_.find(uuid); it != component_uuid_to_column_.end()) if (auto it = component_uuid_to_column_index_.find(uuid); it != component_uuid_to_column_index_.end())
return it->second; return it->second;
return std::nullopt;
}
column * table::column(util::uuid const & uuid) const
{
if (auto index = column_index(uuid))
return columns_[*index].get();
return nullptr; return nullptr;
} }
@ -95,7 +105,10 @@ namespace psemek::ecs::detail
std::vector<std::unique_ptr<detail::column>> columns; std::vector<std::unique_ptr<detail::column>> columns;
for (auto const & column : columns_) for (auto const & column : columns_)
columns.push_back(column->clone()); columns.push_back(column->clone());
return std::make_unique<table>(std::move(columns));
auto result = std::make_unique<table>(std::move(columns));
result->constructors_ = constructors_;
return result;
} }
table * table::get_delayed_table() table * table::get_delayed_table()
@ -128,4 +141,17 @@ namespace psemek::ecs::detail
return non_copyable_components_; return non_copyable_components_;
} }
void table::add_constructor(table_callback callback)
{
constructors_->push_back(std::move(callback));
}
void table::trigger_constructors(container & container, std::uint32_t row)
{
for (auto const & callback : *constructors_)
{
callback(container, *this, row);
}
}
} }

View file

@ -0,0 +1,54 @@
#include <psemek/test/test.hpp>
#include <psemek/ecs/container.hpp>
#include <psemek/ecs/declare_uuid.hpp>
using namespace psemek;
using namespace psemek::ecs;
namespace
{
struct component_1
{
int value;
psemek_ecs_declare_uuid("component_1")
};
struct component_2
{
int value;
psemek_ecs_declare_uuid("component_2")
};
}
test_case(ecs_callback_constructor_create)
{
container container;
int value = 0;
container.constructor<component_1>([&value](component_1 const & c1){
value = c1.value;
});
container.create(component_1{10});
expect_equal(value, 10);
container.create(component_1{20});
expect_equal(value, 20);
container.create(component_2{100});
expect_equal(value, 20);
container.create();
expect_equal(value, 20);
container.create(component_1{30});
expect_equal(value, 30);
container.create(component_1{40}, component_2{200});
expect_equal(value, 40);
}