ECS constructors wip
This commit is contained in:
parent
0b1522722c
commit
028b4e1296
10 changed files with 192 additions and 30 deletions
|
|
@ -19,7 +19,6 @@ namespace psemek::ecs
|
|||
{
|
||||
|
||||
using query_cache = std::shared_ptr<detail::query_cache>;
|
||||
using token = std::shared_ptr<void>;
|
||||
|
||||
// TODO:
|
||||
// - 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
|
||||
* exactly when the entity didn't match the constructor's component types
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* a compilation error
|
||||
* @warning If the constructor modifies the entity's archetype (i.e. attaches or
|
||||
* detaches components), it might be called recursively, leading to
|
||||
* infinite recursion. It is best not to change the archetype from the
|
||||
* constructor.
|
||||
* detaches components), the behavior is undefined
|
||||
* @warning If the constructor destroys the entity, the behavior is undefined
|
||||
*/
|
||||
// TODO: implement
|
||||
template <typename ... Components, typename Function>
|
||||
token constructor(Function && function);
|
||||
void constructor(Function && function);
|
||||
|
||||
/** Register a destructor. Each time an entity is destroyed that has
|
||||
* 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
|
||||
* exactly when the entity did match the destructor's component types
|
||||
* 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
|
||||
* passed to the `apply<Components...>()` call.
|
||||
|
|
@ -337,10 +334,11 @@ namespace psemek::ecs
|
|||
* a compilation error
|
||||
* @warning If the destructor modifies the entity's archetype (i.e. attaches or
|
||||
* detaches components), the behavior is undefined
|
||||
* @warning If the destructor destroys the entity recursively, the behavior is undefined
|
||||
*/
|
||||
// TODO: implement
|
||||
template <typename ... Components, typename Function>
|
||||
token destructor(Function && function);
|
||||
void destructor(Function && function);
|
||||
|
||||
/** Register a component modification callback. Each time an entity that has
|
||||
* the specified set of components is modified, and the modification affects
|
||||
|
|
@ -376,7 +374,7 @@ namespace psemek::ecs
|
|||
*/
|
||||
// TODO: implement
|
||||
template <typename ... Components, typename Function>
|
||||
token watch(Function && function);
|
||||
void watch(Function && function);
|
||||
|
||||
private:
|
||||
detail::entity_list entity_list_;
|
||||
|
|
@ -387,6 +385,8 @@ namespace psemek::ecs
|
|||
std::vector<util::uuid> uuid_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);
|
||||
void do_destroy(handle const & entity);
|
||||
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())
|
||||
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};
|
||||
[[maybe_unused]] accessor accessor = get(handle);
|
||||
|
||||
table->push_row(handle);
|
||||
((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);
|
||||
|
||||
table->trigger_constructors(*this, row);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
|
@ -477,9 +480,9 @@ namespace psemek::ecs
|
|||
template <typename ... Components>
|
||||
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];
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
|
|
@ -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");
|
||||
|
||||
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");
|
||||
|
||||
|
|
@ -602,4 +605,30 @@ namespace psemek::ecs
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ namespace psemek::ecs::detail
|
|||
template <typename ... Components>
|
||||
struct static_apply_helper
|
||||
{
|
||||
static constexpr std::size_t column_count = sizeof...(Components);
|
||||
|
||||
container & parent;
|
||||
std::size_t row_count;
|
||||
handle const * entity_handles_pointer;
|
||||
|
|
@ -132,6 +134,14 @@ namespace psemek::ecs::detail
|
|||
std::size_t i = 0;
|
||||
((pointers[i++] += stride<Components>()), ...);
|
||||
}
|
||||
|
||||
void advance(std::size_t rows)
|
||||
{
|
||||
entity_handles_pointer += rows;
|
||||
|
||||
std::size_t i = 0;
|
||||
((pointers[i++] += stride<Components>() * rows), ...);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
22
libs/ecs/include/psemek/ecs/detail/callback.hpp
Normal file
22
libs/ecs/include/psemek/ecs/detail/callback.hpp
Normal 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)>;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/ecs/detail/callback.hpp>
|
||||
#include <psemek/util/uuid.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
|
@ -13,7 +14,7 @@ namespace psemek::ecs::detail
|
|||
struct query_cache_entry
|
||||
{
|
||||
struct table * table = nullptr;
|
||||
std::vector<column *> columns;
|
||||
std::vector<std::uint32_t> columns_indices;
|
||||
};
|
||||
|
||||
struct query_cache
|
||||
|
|
@ -22,6 +23,8 @@ namespace psemek::ecs::detail
|
|||
std::vector<util::uuid> without_uuids;
|
||||
std::vector<query_cache_entry> entries;
|
||||
|
||||
util::function<table_callback(std::vector<std::uint32_t> const &)> constructor_factory;
|
||||
|
||||
void add(table * table);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <psemek/ecs/handle.hpp>
|
||||
#include <psemek/ecs/detail/component_hash.hpp>
|
||||
#include <psemek/ecs/detail/column.hpp>
|
||||
#include <psemek/ecs/detail/callback.hpp>
|
||||
#include <psemek/util/uuid.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
#include <psemek/util/hash_table.hpp>
|
||||
|
|
@ -26,7 +27,8 @@ namespace psemek::ecs::detail
|
|||
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
|
||||
{
|
||||
|
|
@ -38,11 +40,6 @@ namespace psemek::ecs::detail
|
|||
return columns_;
|
||||
}
|
||||
|
||||
std::size_t column_count() const
|
||||
{
|
||||
return component_uuid_to_column_.size();
|
||||
}
|
||||
|
||||
std::size_t row_count() const
|
||||
{
|
||||
return entity_handles_.size();
|
||||
|
|
@ -75,10 +72,14 @@ namespace psemek::ecs::detail
|
|||
|
||||
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:
|
||||
std::size_t hash_;
|
||||
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_;
|
||||
|
||||
|
|
@ -87,6 +88,8 @@ namespace psemek::ecs::detail
|
|||
std::unique_ptr<table> delayed_table_;
|
||||
|
||||
std::vector<std::type_index> non_copyable_components_;
|
||||
|
||||
std::shared_ptr<std::vector<table_callback>> constructors_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace psemek::ecs::detail
|
|||
{
|
||||
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;
|
||||
for (auto const & uuid : uuids)
|
||||
if (!table->column(uuid))
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@ namespace psemek::ecs::detail
|
|||
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>
|
||||
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;
|
||||
};
|
||||
|
||||
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>
|
||||
struct filter_with;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ namespace psemek::ecs::detail
|
|||
entry.table = table;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,23 +7,33 @@ namespace psemek::ecs::detail
|
|||
table::table(std::vector<std::unique_ptr<detail::column>> columns)
|
||||
{
|
||||
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();
|
||||
hasher(uuid);
|
||||
component_uuid_to_column_.insert({uuid, column.get()});
|
||||
component_uuid_to_column_index_.insert({uuid, i});
|
||||
if (!column->copy_constructible())
|
||||
non_copyable_components_.push_back(column->type());
|
||||
}
|
||||
hash_ = hasher.result;
|
||||
|
||||
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 std::nullopt;
|
||||
}
|
||||
|
||||
column * table::column(util::uuid const & uuid) const
|
||||
{
|
||||
if (auto index = column_index(uuid))
|
||||
return columns_[*index].get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +105,10 @@ namespace psemek::ecs::detail
|
|||
std::vector<std::unique_ptr<detail::column>> columns;
|
||||
for (auto const & column : columns_)
|
||||
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()
|
||||
|
|
@ -128,4 +141,17 @@ namespace psemek::ecs::detail
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
54
libs/ecs/tests/callback.cpp
Normal file
54
libs/ecs/tests/callback.cpp
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue