registry (close #521):

* remove visit
* added storage_proxy_iterator (input iterator category, modeled as a random access iterator)
* added ::storage() (const and non-const) to return an iterable object over all pools
This commit is contained in:
Michele Caini
2021-12-17 16:46:52 +01:00
parent a4aab8458b
commit 3498dea486
6 changed files with 264 additions and 58 deletions

View File

@@ -1208,11 +1208,11 @@ them by copy if needed:
```cpp
// create a copy of an entity component by component
registry.visit([entity, other](auto &&storage) {
if(storage.contains(entity)) {
for(auto &&curr: registry.storage()) {
if(auto &storage = curr.second; storage.contains(entity)) {
storage.emplace(other, storage.get(entity));
}
});
}
```
This is particularly useful to clone entities in an opaque way. In addition, the

View File

@@ -266,17 +266,26 @@ struct basic_handle {
/**
* @brief Visits a handle and returns the pools for its components.
* @sa basic_registry::visit
*
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(id_type, const basic_sparse_set<entity_type> &);
* @endcode
*
* Returned pools are those that contain the entity associated with the
* handle.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void visit(Func &&func) const {
reg->visit([func = std::forward<Func>(func), this](auto &&storage) {
for(auto [id, storage]: reg->storage()) {
if(storage.contains(entt)) {
func(storage);
func(id, storage);
}
});
}
}
private:

View File

@@ -14,6 +14,7 @@
#include "../core/algorithm.hpp"
#include "../core/any.hpp"
#include "../core/fwd.hpp"
#include "../core/iterator.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../core/utility.hpp"
@@ -29,6 +30,141 @@
namespace entt {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename It>
class storage_proxy_iterator {
template<typename Other>
friend class storage_proxy_iterator;
using iterator_traits = std::iterator_traits<It>;
using first_type = typename iterator_traits::value_type::first_type;
using second_type = typename iterator_traits::value_type::second_type::element_type;
public:
using value_type = std::pair<first_type, constness_as_t<second_type, std::remove_reference_t<typename iterator_traits::reference>> &>;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = typename iterator_traits::difference_type;
using iterator_category = std::input_iterator_tag;
storage_proxy_iterator() ENTT_NOEXCEPT = default;
storage_proxy_iterator(const It iter) ENTT_NOEXCEPT
: it{iter} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
storage_proxy_iterator(const storage_proxy_iterator<Other> &other)
: it{other.it} {}
storage_proxy_iterator &operator++() ENTT_NOEXCEPT {
return ++it, *this;
}
storage_proxy_iterator operator++(int) ENTT_NOEXCEPT {
storage_proxy_iterator orig = *this;
return ++(*this), orig;
}
storage_proxy_iterator &operator--() ENTT_NOEXCEPT {
return --it, *this;
}
storage_proxy_iterator operator--(int) ENTT_NOEXCEPT {
storage_proxy_iterator orig = *this;
return operator--(), orig;
}
storage_proxy_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
it += value;
return *this;
}
storage_proxy_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
storage_proxy_iterator copy = *this;
return (copy += value);
}
storage_proxy_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
storage_proxy_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
[[nodiscard]] reference operator[](const difference_type value) const {
return {it[value].first, *it[value].second};
}
[[nodiscard]] reference operator*() const {
return {it->first, *it->second};
}
[[nodiscard]] pointer operator->() const {
return operator*();
}
template<typename ILhs, typename IRhs>
friend auto operator-(const storage_proxy_iterator<ILhs> &, const storage_proxy_iterator<IRhs> &) ENTT_NOEXCEPT;
template<typename ILhs, typename IRhs>
friend bool operator==(const storage_proxy_iterator<ILhs> &, const storage_proxy_iterator<IRhs> &) ENTT_NOEXCEPT;
template<typename ILhs, typename IRhs>
friend bool operator<(const storage_proxy_iterator<ILhs> &, const storage_proxy_iterator<IRhs> &) ENTT_NOEXCEPT;
private:
It it;
};
template<typename ILhs, typename IRhs>
[[nodiscard]] auto operator-(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it - rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator==(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it == rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator!=(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it < rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<=(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs > rhs);
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>=(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
} // namespace internal
/**
* Internal details not to be documented.
* @endcond
*/
/**
* @brief Fast and reliable entity-component system.
* @tparam Entity A valid entity type (see entt_traits for more details).
@@ -181,6 +317,23 @@ public:
return *this;
}
/**
* @brief Returns an iterable object to use to _visit_ a registry.
*
* The iterable object returns a pair that contains the name and a reference
* to the current storage.
*
* @return An iterable object to use to _visit_ the registry.
*/
[[nodiscard]] auto storage() ENTT_NOEXCEPT {
return iterable_adaptor{internal::storage_proxy_iterator{pools.begin()}, internal::storage_proxy_iterator{pools.end()}};
}
/*! @copydoc storage */
[[nodiscard]] auto storage() const ENTT_NOEXCEPT {
return iterable_adaptor{internal::storage_proxy_iterator{pools.cbegin()}, internal::storage_proxy_iterator{pools.cend()}};
}
/**
* @brief Returns the storage for a given component type.
* @tparam Component Type of component of which to return the storage.
@@ -583,7 +736,7 @@ public:
/**
* @brief Patches the given component for an entity.
*
* The signature of the functions should be equivalent to the following:
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(Component &);
@@ -1266,27 +1419,6 @@ public:
assure<To>().respect(assure<From>());
}
/**
* @brief Visits a registry and returns the pools for its components.
*
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(const basic_sparse_set<entity_type> &);
* @endcode
*
* Returned pools are those of the components managed by the registry.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void visit(Func func) const {
for(auto &&curr: pools) {
func(*curr.second);
}
}
/**
* @brief Binds an object to the context of the registry.
*

View File

@@ -169,7 +169,10 @@ TEST(BasicHandle, Component) {
ASSERT_TRUE(registry.storage<double>().empty());
ASSERT_EQ(0u, (handle.remove<char, double>()));
handle.visit([](const auto &pool) { ASSERT_EQ(entt::type_id<int>(), pool.type()); });
handle.visit([&handle](const auto id, const auto &pool) {
ASSERT_EQ(id, entt::type_id<int>().hash());
ASSERT_TRUE(pool.contains(handle.entity()));
});
ASSERT_TRUE((handle.any_of<int, char, double>()));
ASSERT_FALSE((handle.all_of<int, char, double>()));

View File

@@ -1854,26 +1854,6 @@ TEST(Registry, AssignEntities) {
ASSERT_EQ(traits_type::to_entity(other.create()), traits_type::to_integral(entities[1]));
}
TEST(Registry, Visit) {
entt::registry registry;
const auto entity = registry.create();
const auto other = registry.create();
registry.emplace<int>(entity);
registry.emplace<double>(other);
registry.emplace<char>(entity);
bool hasType[3]{};
registry.visit([&hasType](const auto &pool) {
hasType[0] = hasType[0] || (pool.type() == entt::type_id<int>());
hasType[1] = hasType[1] || (pool.type() == entt::type_id<double>());
hasType[2] = hasType[2] || (pool.type() == entt::type_id<char>());
});
ASSERT_TRUE(hasType[0] && hasType[1] && hasType[2]);
}
TEST(Registry, ScramblingPoolsIsAllowed) {
entt::registry registry;
registry.on_destroy<int>().connect<&listener::sort<int>>();
@@ -1926,3 +1906,85 @@ TEST(Registry, RuntimePools) {
ASSERT_FALSE(storage.contains(entity));
ASSERT_FALSE(registry.any_of<empty_type>(entity));
}
TEST(Registry, StorageProxy) {
using namespace entt::literals;
entt::registry registry;
const auto entity = registry.create();
auto &storage = registry.storage<int>("int"_hs);
storage.emplace(entity);
for(auto [id, pool]: registry.storage()) {
static_assert(std::is_same_v<decltype(pool), entt::sparse_set &>);
static_assert(std::is_same_v<decltype(id), const entt::id_type>);
ASSERT_TRUE(pool.contains(entity));
ASSERT_EQ(std::addressof(storage), std::addressof(pool));
ASSERT_EQ(id, "int"_hs);
}
for(auto &&curr: std::as_const(registry).storage()) {
static_assert(std::is_same_v<decltype(curr.second), const entt::sparse_set &>);
static_assert(std::is_same_v<decltype(curr.first), const entt::id_type>);
ASSERT_TRUE(curr.second.contains(entity));
ASSERT_EQ(std::addressof(storage), std::addressof(curr.second));
ASSERT_EQ(curr.first, "int"_hs);
}
}
TEST(Registry, StorageProxyIterator) {
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
auto test = [entity](auto iterable) {
auto end{iterable.begin()};
decltype(end) begin{};
begin = iterable.end();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.cbegin());
ASSERT_EQ(end, iterable.cend());
ASSERT_NE(begin, end);
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin--, iterable.end());
ASSERT_EQ(begin + 1, iterable.end());
ASSERT_EQ(end - 1, iterable.begin());
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(--begin, iterable.begin());
ASSERT_EQ(begin += 1, iterable.end());
ASSERT_EQ(begin -= 1, iterable.begin());
ASSERT_EQ(begin + (end - begin), iterable.end());
ASSERT_EQ(begin - (begin - end), iterable.end());
ASSERT_EQ(end - (end - begin), iterable.begin());
ASSERT_EQ(end + (begin - end), iterable.begin());
ASSERT_EQ(begin[0u].first, iterable.begin()->first);
ASSERT_EQ(std::addressof(begin[0u].second), std::addressof((*iterable.begin()).second));
ASSERT_LT(begin, end);
ASSERT_LE(begin, iterable.begin());
ASSERT_GT(end, begin);
ASSERT_GE(end, iterable.end());
ASSERT_EQ(begin[0u].first, entt::type_id<int>().hash());
ASSERT_TRUE(begin[0u].second.contains(entity));
};
test(registry.storage());
test(std::as_const(registry).storage());
decltype(std::as_const(registry).storage().begin()) cit = registry.storage().begin();
ASSERT_EQ(cit, registry.storage().begin());
ASSERT_NE(cit, std::as_const(registry).storage().end());
}

View File

@@ -22,17 +22,17 @@ TEST(Example, EntityCopy) {
ASSERT_FALSE((registry.any_of<int, char, double>(dst)));
ASSERT_FALSE(custom.contains(dst));
registry.visit([src, dst](auto &&storage) {
// discard chars because why not, this is just an example after all
if(storage.type() != entt::type_id<char>() && storage.contains(src)) {
for(auto [id, storage]: registry.storage()) {
// discard the custom storage because why not, this is just an example after all
if(id != "custom"_hs && storage.contains(src)) {
storage.emplace(dst, storage.get(src));
}
});
}
ASSERT_TRUE((registry.all_of<int>(dst)));
ASSERT_FALSE((registry.any_of<char, double>(dst)));
ASSERT_TRUE(custom.contains(dst));
ASSERT_TRUE((registry.all_of<int, char>(dst)));
ASSERT_FALSE((registry.all_of<double>(dst)));
ASSERT_FALSE(custom.contains(dst));
ASSERT_EQ(registry.get<int>(dst), 42);
ASSERT_EQ(custom.get(dst), 1.);
ASSERT_EQ(registry.get<char>(dst), 'c');
}