sparse_set/storage:

* refine the try_erase to make it accept an iterator and a count (easier to work with at the call site)
* added a test for the stable clear
* added a note for future improvements
This commit is contained in:
Michele Caini
2022-01-26 12:32:59 +01:00
parent 2e89b3b5b9
commit 37497295fb
4 changed files with 48 additions and 34 deletions

6
TODO
View File

@@ -4,9 +4,7 @@
* add examples (and credits) from @alanjfs :)
WIP:
* investigate: meta as_type_t<T> for getters and the like
* investigate: add the possibility of disabling entities without deleting components thanks to the new full check
* fast-contains for sparse sets (low prio but nice-to-have)
* reintroduce swap-and-pop vs in-place-pop difference for perf reasons
* runtime events (dispatcher/emitter), runtime context variables...
* registry: remove reference to basic_sparse_set<E>
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
@@ -16,10 +14,8 @@ WIP:
* customizable any_vtable, sfinae-friendly definition and op::custom for user-def
* resource, forward the id to the loader from the cache and if constexpr the call to load, update doc and describe customization points
* add user data to type_info
* headless (sparse set only) view
* write documentation for custom storages and views!!
* make runtime views use opaque storage and therefore return also elements.
* add exclude-only views to combine with packs
* entity-aware observer, add observer functions aside observer class
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
* snapshot: support for range-based archives

View File

@@ -234,26 +234,26 @@ protected:
/**
* @brief Erases an entity from a sparse set.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @param it Iterator to the first element to erase.
* @param count Number of elements to erase.
*/
virtual void try_erase(const basic_iterator first, const basic_iterator last) {
for(auto it = first; it != last; ++it) {
const auto pos = static_cast<size_type>(it.index());
auto &ref = sparse_ref(*it);
if(mode == deletion_policy::in_place) {
packed[pos] = std::exchange(free_list, entity_traits::combine(static_cast<typename entity_traits::entity_type>(pos), entity_traits::reserved));
} else {
packed[pos] = packed.back();
sparse_ref(packed.back()) = entity_traits::combine(static_cast<typename entity_traits::entity_type>(pos), entity_traits::to_integral(packed.back()));
virtual void try_erase(basic_iterator it, const std::size_t count) {
if(mode == deletion_policy::in_place) {
for(const auto last = it + count; it != last; ++it) {
packed[it.index()] = std::exchange(free_list, entity_traits::combine(static_cast<typename entity_traits::entity_type>(it.index()), entity_traits::reserved));
sparse_ref(*it) = null;
}
} else {
for(const auto last = it + count; it != last; ++it) {
auto &ref = sparse_ref(*it);
packed[it.index()] = packed.back();
sparse_ref(packed.back()) = entity_traits::combine(static_cast<typename entity_traits::entity_type>(it.index()), entity_traits::to_integral(packed.back()));
// unnecessary but it helps to detect nasty bugs
ENTT_ASSERT((packed.back() = tombstone, true), "");
packed.pop_back();
// lazy self-assignment guard
ref = null;
}
// lazy self-assignment guard
ref = null;
}
}
@@ -696,8 +696,7 @@ public:
* @param entt A valid identifier.
*/
void erase(const entity_type entt) {
const auto it = --(end() - index(entt));
try_erase(it, it + 1u);
try_erase(--(end() - index(entt)), 1u);
}
/**
@@ -712,7 +711,7 @@ public:
template<typename It>
void erase(It first, It last) {
if constexpr(std::is_same_v<It, basic_iterator>) {
try_erase(first, last);
try_erase(first, static_cast<size_type>(std::distance(first, last)));
} else {
for(; first != last; ++first) {
erase(*first);
@@ -909,7 +908,7 @@ public:
remove(entity);
}
} else {
try_erase(begin(), end());
try_erase(begin(), size());
}
}

View File

@@ -277,7 +277,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
alloc_traits::construct(packed.second(), to_address(elem), std::forward<Args>(args)...);
}
ENTT_CATCH {
base_type::try_erase(it, it + 1u);
base_type::try_erase(it, 1u);
ENTT_THROW;
}
@@ -326,16 +326,16 @@ private:
protected:
/**
* @brief Erases an element from a storage.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @param it Iterator to the first element to erase.
* @param count Number of elements to erase.
*/
void try_erase(const typename underlying_type::basic_iterator first, const typename underlying_type::basic_iterator last) override {
for(auto it = first; it != last; ++it) {
void try_erase(typename underlying_type::basic_iterator it, const std::size_t count) override {
for(const auto last = it + count; it != last; ++it) {
const auto pos = static_cast<size_type>(it.index());
auto &elem = element_at(comp_traits::in_place_delete ? pos : (base_type::size() - 1u));
[[maybe_unused]] auto unused = std::exchange(element_at(pos), std::move(elem));
std::destroy_at(std::addressof(elem));
base_type::try_erase(it, it + 1u);
base_type::try_erase(it, 1u);
}
}
@@ -895,14 +895,13 @@ public:
*/
template<typename Type>
class sigh_storage_mixin final: public Type {
void try_erase(const typename Type::basic_iterator first, const typename Type::basic_iterator last) override {
void try_erase(typename Type::basic_iterator it, const std::size_t count) override {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
for(auto it = first; it != last; ++it) {
for(const auto last = it + count; it != last; ++it) {
const auto entt = *it;
destruction.publish(*owner, entt);
const auto curr = Type::find(entt);
Type::try_erase(curr, curr + 1u);
Type::try_erase(Type::find(entt), 1u);
}
}

View File

@@ -811,6 +811,26 @@ TEST(SparseSet, Clear) {
set.clear();
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
}
TEST(SparseSet, StableClear) {
entt::sparse_set set{entt::deletion_policy::in_place};
set.emplace(entt::entity{3});
set.emplace(entt::entity{42});
set.emplace(entt::entity{9});
ASSERT_FALSE(set.empty());
set.clear();
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 3u);
ASSERT_EQ(set.find(entt::entity{3}), set.end());
ASSERT_EQ(set.find(entt::entity{42}), set.end());
ASSERT_EQ(set.find(entt::entity{9}), set.end());
}
TEST(SparseSet, Iterator) {