From 1265b816d387c9defe64dd296d38f804bd5d7c9c Mon Sep 17 00:00:00 2001 From: lisyarus Date: Thu, 13 Aug 2026 18:03:57 +0300 Subject: [PATCH] Replace ecs::container::describe() with describe_short() and describe_detailed(), and introduce entity inspection API --- libs/ecs/include/psemek/ecs/accessor.hpp | 40 ++++++++++++---- libs/ecs/include/psemek/ecs/container.hpp | 30 ++++++++++-- libs/ecs/include/psemek/ecs/detail/column.hpp | 48 +++++-------------- .../psemek/ecs/detail/component_registry.hpp | 41 +++++++++++----- .../include/psemek/ecs/detail/describe.hpp | 39 --------------- libs/ecs/include/psemek/ecs/detail/table.hpp | 4 +- .../include/psemek/ecs/detail/to_string.hpp | 43 +++++++++++++++++ libs/ecs/include/psemek/ecs/inspect.hpp | 19 ++++++++ libs/ecs/source/container.cpp | 45 +++++++++++++++-- libs/ecs/source/detail/table.cpp | 21 ++------ 10 files changed, 205 insertions(+), 125 deletions(-) delete mode 100644 libs/ecs/include/psemek/ecs/detail/describe.hpp create mode 100644 libs/ecs/include/psemek/ecs/detail/to_string.hpp create mode 100644 libs/ecs/include/psemek/ecs/inspect.hpp diff --git a/libs/ecs/include/psemek/ecs/accessor.hpp b/libs/ecs/include/psemek/ecs/accessor.hpp index 5f0e762d..1900a1be 100644 --- a/libs/ecs/include/psemek/ecs/accessor.hpp +++ b/libs/ecs/include/psemek/ecs/accessor.hpp @@ -3,8 +3,6 @@ #include #include -#include - namespace psemek::ecs { @@ -55,6 +53,19 @@ namespace psemek::ecs template Component const & get() const; + /** Obtain the component by uuid of the accessed entity. + * As the runtime type of the component is unknown, + * the function returns pointer-to-void. + * + * @param uuid The uuid of component to obtain + * @return A pointer to the component of the specified type, or a null + * pointer if the entity doesn't contain a component of this type + * @pre The accessor is valid + */ + void * get_if(util::uuid const & uuid); + + void const * get_if(util::uuid const & uuid) const; + /** Check if the entity contains this component type. * * @tparam Component The type of component to obtain @@ -98,12 +109,7 @@ namespace psemek::ecs Component const * accessor::get_if() const { util::uuid const uuid = std::remove_const_t::uuid(); - - auto column = table_->column(uuid); - if (!column) - return nullptr; - - return reinterpret_cast(column->data() + detail::stride>() * row_); + return reinterpret_cast(get_if(uuid)); } template @@ -111,7 +117,7 @@ namespace psemek::ecs { if (auto ptr = get_if()) return *ptr; - throw component_not_found_exception(typeid(std::remove_const_t), table_->entity_handles()[row_], table_->describe(row_)); + throw component_not_found_exception(typeid(std::remove_const_t), table_->entity_handles()[row_], table_->describe_short()); } template @@ -119,7 +125,21 @@ namespace psemek::ecs { if (auto ptr = get_if()) return *ptr; - throw component_not_found_exception(typeid(std::remove_const_t), table_->entity_handles()[row_], table_->describe(row_)); + throw component_not_found_exception(typeid(std::remove_const_t), table_->entity_handles()[row_], table_->describe_short()); + } + + inline void * accessor::get_if(util::uuid const & uuid) + { + return const_cast(static_cast(this)->get_if(uuid)); + } + + inline void const * accessor::get_if(util::uuid const & uuid) const + { + auto column = table_->column(uuid); + if (!column) + return nullptr; + + return column->data() + column->stride() * row_; } template diff --git a/libs/ecs/include/psemek/ecs/container.hpp b/libs/ecs/include/psemek/ecs/container.hpp index 4f567503..82fe81ef 100644 --- a/libs/ecs/include/psemek/ecs/container.hpp +++ b/libs/ecs/include/psemek/ecs/container.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -95,14 +96,33 @@ namespace psemek::ecs */ accessor get(handle entity); - /** Compute a string representation of an entity, based on the contained components. - * A component has to implement a to_string() method in order to provide extra info. + /** Inspect the entity, i.e. enumerate the entitie's components. * - * @param entity A handle to the entity to describe - * @return A string representation of an entity + * @param entity A handle to the entity to inspect + * @return A list of component uuids & metadata * @pre The entity was previously obtained by a `create()` call and is `alive()` */ - std::string describe(handle entity) const; + entity_inspection_info inspect(handle entity) const; + + /** Compute a short string representation of an entity, based on the contained components. + * The short version just enumerates the component types, separated by ';'. + * + * @param entity A handle to the entity to describe + * @return A short string representation of an entity + * @pre The entity was previously obtained by a `create()` call and is `alive()` + */ + std::string describe_short(handle entity) const; + + /** Compute a detailed string representation of an entity, based on the contained components. + * A component has to implement a to_string() method in order to provide extra info. + * The detailed version lists all components together with their data (as provided by + * component.to_string) in a large multi-line string. + * + * @param entity A handle to the entity to describe + * @return A detailed string representation of an entity + * @pre The entity was previously obtained by a `create()` call and is `alive()` + */ + std::string describe_detailed(handle entity) const; /** Check if the entity can be cloned. * diff --git a/libs/ecs/include/psemek/ecs/detail/column.hpp b/libs/ecs/include/psemek/ecs/detail/column.hpp index d06472a2..1df73cd0 100644 --- a/libs/ecs/include/psemek/ecs/detail/column.hpp +++ b/libs/ecs/include/psemek/ecs/detail/column.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -33,6 +33,11 @@ namespace psemek::ecs::detail return uuid_; } + std::string const & name() const + { + return name_; + } + std::type_index const & type() const { return type_; @@ -54,21 +59,20 @@ namespace psemek::ecs::detail virtual std::size_t memory_usage() const = 0; - virtual std::string describe() const = 0; - virtual std::string describe(std::uint32_t row) const = 0; - virtual ~column() = default; protected: std::uint8_t * data_ = nullptr; std::size_t stride_; util::uuid uuid_; + std::string name_; std::type_index type_; bool copy_constructible_; - column(std::size_t stride, util::uuid const & uuid, std::type_index const & type, bool copy_constructible) + column(std::size_t stride, util::uuid const & uuid, std::string name, std::type_index const & type, bool copy_constructible) : stride_(stride) , uuid_(uuid) + , name_(std::move(name)) , type_(type) , copy_constructible_(copy_constructible) {} @@ -91,9 +95,6 @@ namespace psemek::ecs::detail std::size_t memory_usage() const override; - std::string describe() const override; - std::string describe(std::uint32_t row) const override; - ~column_impl() override; private: @@ -120,15 +121,12 @@ namespace psemek::ecs::detail std::size_t memory_usage() const override; - std::string describe() const override; - std::string describe(std::uint32_t row) const override; - ~column_impl() override; }; template column_impl::column_impl() - : column(detail::stride(), Component::uuid(), typeid(Component), std::is_copy_constructible_v) + : column(detail::stride(), Component::uuid(), component_name(), typeid(Component), std::is_copy_constructible_v) {} template @@ -218,18 +216,6 @@ namespace psemek::ecs::detail return sizeof(Component) * row_count_; } - template - std::string column_impl::describe() const - { - return detail::describe(); - } - - template - std::string column_impl::describe(std::uint32_t row) const - { - return detail::describe(reinterpret_cast(data_)[row]); - } - template column_impl::~column_impl() { @@ -267,7 +253,7 @@ namespace psemek::ecs::detail template column_impl::column_impl() - : column(detail::stride(), Component::uuid(), typeid(Component), std::is_copy_constructible_v) + : column(detail::stride(), Component::uuid(), component_name(), typeid(Component), std::is_copy_constructible_v) { data_ = reinterpret_cast(new Component[1]); } @@ -308,18 +294,6 @@ namespace psemek::ecs::detail return sizeof(Component); } - template - std::string column_impl::describe() const - { - return detail::describe(); - } - - template - std::string column_impl::describe(std::uint32_t) const - { - return detail::describe(*reinterpret_cast(data_)); - } - template column_impl::~column_impl() { diff --git a/libs/ecs/include/psemek/ecs/detail/component_registry.hpp b/libs/ecs/include/psemek/ecs/detail/component_registry.hpp index 3b24b384..00db42cd 100644 --- a/libs/ecs/include/psemek/ecs/detail/component_registry.hpp +++ b/libs/ecs/include/psemek/ecs/detail/component_registry.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -32,36 +33,54 @@ namespace psemek::ecs::detail struct component_registry { + struct component_info + { + std::string name; + std::type_info const * type_info; + util::function()> column_factory; + util::function to_string; + }; + template void register_component() { auto const & uuid = Component::uuid(); - if (auto it = types_.find(uuid); it != types_.end()) + if (auto it = components_.find(uuid); it != components_.end()) { - if (it->second != &typeid(Component)) - throw duplicate_uuid_exception(uuid, *(it->second), typeid(Component)); + if (it->second.type_info != &typeid(Component)) + throw duplicate_uuid_exception(uuid, *(it->second.type_info), typeid(Component)); else return; } - types_.insert({uuid, &typeid(Component)}); - - column_factories_.insert({uuid, []{ + component_info & info = components_[uuid]; + info.name = component_name(); + info.type_info = &typeid(Component); + info.column_factory = []{ return std::make_unique>(); - }}); + }; + info.to_string = [](void const * data){ + return component_to_string(*static_cast(data)); + }; } std::unique_ptr create_column(util::uuid const & uuid) const { - if (auto it = column_factories_.find(uuid); it != column_factories_.end()) - return it->second(); + if (auto it = components_.find(uuid); it != components_.end()) + return it->second.column_factory(); + return nullptr; + } + + component_info const * get_info(util::uuid const & uuid) const + { + if (auto it = components_.find(uuid); it != components_.end()) + return &it->second; return nullptr; } private: - util::hash_map types_; - util::hash_map()>> column_factories_; + util::hash_map components_; }; } diff --git a/libs/ecs/include/psemek/ecs/detail/describe.hpp b/libs/ecs/include/psemek/ecs/detail/describe.hpp deleted file mode 100644 index 5c25051f..00000000 --- a/libs/ecs/include/psemek/ecs/detail/describe.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include - -#include - -namespace psemek::ecs::detail -{ - - template - std::string describe() - { - if constexpr (requires {Component::to_string();}) - { - return Component::to_string(); - } - else - { - auto name = util::type_name(); - if (auto pos = name.find_last_of(':'); pos != std::string::npos) - name = name.substr(pos + 1); - return name; - } - } - - template - std::string describe(Component const & component) - { - if constexpr (requires {component.to_string();}) - { - return component.to_string(); - } - else - { - return describe(); - } - } - -} diff --git a/libs/ecs/include/psemek/ecs/detail/table.hpp b/libs/ecs/include/psemek/ecs/detail/table.hpp index 5dea109a..0a64f67d 100644 --- a/libs/ecs/include/psemek/ecs/detail/table.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table.hpp @@ -11,7 +11,6 @@ #include #include #include -#include namespace psemek::ecs::detail { @@ -81,8 +80,7 @@ namespace psemek::ecs::detail std::size_t memory_usage() const; - std::string describe() const; - std::string describe(std::uint32_t row) const; + std::string describe_short() const; protected: std::uint64_t hash_; diff --git a/libs/ecs/include/psemek/ecs/detail/to_string.hpp b/libs/ecs/include/psemek/ecs/detail/to_string.hpp new file mode 100644 index 00000000..77999071 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/to_string.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + +namespace psemek::ecs::detail +{ + + template + std::string component_name() + { + if constexpr (requires {Component::name();}) + { + return Component::name(); + } + else + { + static std::string const result = []{ + std::string name = util::type_name(); + if (auto pos = name.find_last_of(':'); pos != std::string::npos) + name = name.substr(pos + 1); + return name; + }(); + + return result; + } + } + + template + std::string component_to_string(Component const & component) + { + if constexpr (requires {component.to_string();}) + { + return component.to_string(); + } + else + { + return ""; + } + } + +} diff --git a/libs/ecs/include/psemek/ecs/inspect.hpp b/libs/ecs/include/psemek/ecs/inspect.hpp new file mode 100644 index 00000000..878b55ad --- /dev/null +++ b/libs/ecs/include/psemek/ecs/inspect.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace psemek::ecs +{ + + struct entity_inspection_info + { + struct component_inspection_info + { + util::uuid uuid; + detail::component_registry::component_info const * info; + }; + + std::vector components; + }; + +} diff --git a/libs/ecs/source/container.cpp b/libs/ecs/source/container.cpp index 5396c6e0..09cf8ab6 100644 --- a/libs/ecs/source/container.cpp +++ b/libs/ecs/source/container.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -52,11 +53,47 @@ namespace psemek::ecs return {data.table, data.row}; } - std::string container::describe(handle entity) const + entity_inspection_info container::inspect(handle entity) const { - assert(alive(entity)); + entity_inspection_info result; auto const data = entity_list_.get_entities()[entity.id]; - return data.table->describe(data.row); + for (auto const & column : data.table->columns()) + { + auto const uuid = column->uuid(); + result.components.push_back({uuid, component_registry_.get_info(uuid)}); + } + return result; + } + + std::string container::describe_short(handle entity) const + { + return entity_list_.get_entities()[entity.id].table->describe_short(); + } + + std::string container::describe_detailed(handle entity) const + { + static std::string const indent = " "; + + std::string result; + + auto const data = entity_list_.get_entities()[entity.id]; + for (auto const & column : data.table->columns()) + { + auto const info = component_registry_.get_info(column->uuid()); + result += info->name; + result += "\n"; + + auto const ptr = column->data() + column->stride() * data.row; + auto value = info->to_string(ptr); + if (!value.empty()) + { + result += indent; + result += util::replace_all(util::trim(std::move(value)), "\n", "\n" + indent); + result += "\n"; + } + } + + return util::trim(result); } bool container::can_clone(handle entity) const @@ -190,7 +227,7 @@ namespace psemek::ecs table_container_.apply([&](detail::table & table) { - result.tables.push_back({table.describe(), table.row_count()}); + result.tables.push_back({table.describe_short(), table.row_count()}); }, {}, {}); return result; diff --git a/libs/ecs/source/detail/table.cpp b/libs/ecs/source/detail/table.cpp index 27f8011f..cad450ca 100644 --- a/libs/ecs/source/detail/table.cpp +++ b/libs/ecs/source/detail/table.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -190,26 +191,14 @@ namespace psemek::ecs::detail return result; } - std::string table::describe() const + std::string table::describe_short() const { std::string result; - for (std::size_t i = 0; i < columns_.size(); ++i) + for (auto const & column : columns_) { - if (i > 0) + if (!result.empty()) result += ";"; - result += columns_[i]->describe(); - } - return result; - } - - std::string table::describe(std::uint32_t row) const - { - std::string result; - for (std::size_t i = 0; i < columns_.size(); ++i) - { - if (i > 0) - result += ";"; - result += columns_[i]->describe(row); + result += column->name(); } return result; }