#ifndef ENTT_ENTITY_SNAPSHOT_HPP #define ENTT_ENTITY_SNAPSHOT_HPP #include #include #include #include #include #include #include "../config/config.h" #include "../container/dense_map.hpp" #include "../core/type_traits.hpp" #include "entity.hpp" #include "fwd.hpp" #include "view.hpp" namespace entt { /*! @cond TURN_OFF_DOXYGEN */ namespace internal { template void orphans(Registry ®istry) { auto &storage = registry.template storage(); for(auto entt: storage) { if(registry.orphan(entt)) { storage.erase(entt); } } } } // namespace internal /*! @endcond */ /** * @brief Utility class to create snapshots from a registry. * * A _snapshot_ can be either a dump of the entire registry or a narrower * selection of elements of interest.
* This type can be used in both cases if provided with a correctly configured * output archive. * * @tparam Registry Basic registry type. */ template class basic_snapshot { static_assert(!std::is_const_v, "Non-const registry type required"); using traits_type = entt_traits; public: /*! Basic registry type. */ using registry_type = Registry; /*! @brief Underlying entity identifier. */ using entity_type = typename registry_type::entity_type; /** * @brief Constructs an instance that is bound to a given registry. * @param source A valid reference to a registry. */ basic_snapshot(const registry_type &source) noexcept : reg{&source} {} /*! @brief Default copy constructor, deleted on purpose. */ basic_snapshot(const basic_snapshot &) = delete; /*! @brief Default move constructor. */ basic_snapshot(basic_snapshot &&) noexcept = default; /*! @brief Default destructor. */ ~basic_snapshot() noexcept = default; /** * @brief Default copy assignment operator, deleted on purpose. * @return This snapshot. */ basic_snapshot &operator=(const basic_snapshot &) = delete; /** * @brief Default move assignment operator. * @return This snapshot. */ basic_snapshot &operator=(basic_snapshot &&) noexcept = default; /** * @brief Serializes all elements of a type with associated identifiers. * @tparam Type Type of elements to serialize. * @tparam Archive Type of output archive. * @param archive A valid reference to an output archive. * @param id Optional name used to map the storage within the registry. * @return An object of this type to continue creating the snapshot. */ template const basic_snapshot &get(Archive &archive, const id_type id = type_hash::value()) const { if(const auto *storage = reg->template storage(id); storage) { const typename registry_type::common_type &base = *storage; archive(static_cast(storage->size())); if constexpr(std::is_same_v) { archive(static_cast(storage->free_list())); for(auto first = base.rbegin(), last = base.rend(); first != last; ++first) { archive(*first); } } else if constexpr(registry_type::template storage_for_type::storage_policy == deletion_policy::in_place) { for(auto it = base.rbegin(), last = base.rend(); it != last; ++it) { if(const auto entt = *it; entt == tombstone) { archive(static_cast(null)); } else { archive(entt); std::apply([&archive](auto &&...args) { (archive(std::forward(args)), ...); }, storage->get_as_tuple(entt)); } } } else { for(auto elem: storage->reach()) { std::apply([&archive](auto &&...args) { (archive(std::forward(args)), ...); }, elem); } } } else { archive(typename traits_type::entity_type{}); } return *this; } /** * @brief Serializes all elements of a type with associated identifiers for * the entities in a range. * @tparam Type Type of elements to serialize. * @tparam Archive Type of output archive. * @tparam It Type of input iterator. * @param archive A valid reference to an output archive. * @param first An iterator to the first element of the range to serialize. * @param last An iterator past the last element of the range to serialize. * @param id Optional name used to map the storage within the registry. * @return An object of this type to continue creating the snapshot. */ template const basic_snapshot &get(Archive &archive, It first, It last, const id_type id = type_hash::value()) const { static_assert(!std::is_same_v, "Entity types not supported"); if(const auto *storage = reg->template storage(id); storage && !storage->empty()) { archive(static_cast(std::distance(first, last))); for(; first != last; ++first) { if(const auto entt = *first; storage->contains(entt)) { archive(entt); std::apply([&archive](auto &&...args) { (archive(std::forward(args)), ...); }, storage->get_as_tuple(entt)); } else { archive(static_cast(null)); } } } else { archive(typename traits_type::entity_type{}); } return *this; } private: const registry_type *reg; }; /** * @brief Utility class to restore a snapshot as a whole. * * A snapshot loader requires that the destination registry be empty and loads * all the data at once while keeping intact the identifiers that the entities * originally had.
* An example of use is the implementation of a save/restore utility. * * @tparam Registry Basic registry type. */ template class basic_snapshot_loader { static_assert(!std::is_const_v, "Non-const registry type required"); using traits_type = entt_traits; public: /*! Basic registry type. */ using registry_type = Registry; /*! @brief Underlying entity identifier. */ using entity_type = typename registry_type::entity_type; /** * @brief Constructs an instance that is bound to a given registry. * @param source A valid reference to a registry. */ basic_snapshot_loader(registry_type &source) noexcept : reg{&source} { // restoring a snapshot as a whole requires a clean registry ENTT_ASSERT(reg->template storage().free_list() == 0u, "Registry must be empty"); } /*! @brief Default copy constructor, deleted on purpose. */ basic_snapshot_loader(const basic_snapshot_loader &) = delete; /*! @brief Default move constructor. */ basic_snapshot_loader(basic_snapshot_loader &&) noexcept = default; /*! @brief Default destructor. */ ~basic_snapshot_loader() noexcept = default; /** * @brief Default copy assignment operator, deleted on purpose. * @return This loader. */ basic_snapshot_loader &operator=(const basic_snapshot_loader &) = delete; /** * @brief Default move assignment operator. * @return This loader. */ basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default; /** * @brief Restores all elements of a type with associated identifiers. * @tparam Type Type of elements to restore. * @tparam Archive Type of input archive. * @param archive A valid reference to an input archive. * @param id Optional name used to map the storage within the registry. * @return A valid loader to continue restoring data. */ template basic_snapshot_loader &get(Archive &archive, const id_type id = type_hash::value()) { auto &storage = reg->template storage(id); typename traits_type::entity_type length{}; archive(length); if constexpr(std::is_same_v) { typename traits_type::entity_type count{}; storage.reserve(length); archive(count); for(entity_type entity = null; length; --length) { archive(entity); storage.emplace(entity); } storage.free_list(count); } else { auto &other = reg->template storage(); entity_type entt{null}; while(length--) { if(archive(entt); entt != null) { const auto entity = other.contains(entt) ? entt : other.emplace(entt); ENTT_ASSERT(entity == entt, "Entity not available for use"); if constexpr(std::tuple_size_v == 0u) { storage.emplace(entity); } else { Type elem{}; archive(elem); storage.emplace(entity, std::move(elem)); } } } } return *this; } /** * @brief Destroys those entities that have no elements. * * In case all the entities were serialized but only part of the elements * was saved, it could happen that some of the entities have no elements * once restored.
* This function helps to identify and destroy those entities. * * @return A valid loader to continue restoring data. */ basic_snapshot_loader &orphans() { internal::orphans(*reg); return *this; } private: registry_type *reg; }; /** * @brief Utility class for _continuous loading_. * * A _continuous loader_ is designed to load data from a source registry to a * (possibly) non-empty destination. The loader can accommodate in a registry * more than one snapshot in a sort of _continuous loading_ that updates the * destination one step at a time.
* Identifiers that entities originally had are not transferred to the target. * Instead, the loader maps remote identifiers to local ones while restoring a * snapshot.
* An example of use is the implementation of a client-server application with * the requirement of transferring somehow parts of the representation side to * side. * * @tparam Registry Basic registry type. */ template class basic_continuous_loader { static_assert(!std::is_const_v, "Non-const registry type required"); using traits_type = entt_traits; void restore(typename Registry::entity_type entt) { if(const auto entity = to_entity(entt); remloc.contains(entity) && remloc[entity].first == entt) { if(!reg->valid(remloc[entity].second)) { remloc[entity].second = reg->create(); } } else { remloc.insert_or_assign(entity, std::make_pair(entt, reg->create())); } } template auto update(int, Container &container) -> decltype(typename Container::mapped_type{}, void()) { // map like container Container other; for(auto &&pair: container) { using first_type = std::remove_const_t::first_type>; using second_type = typename std::decay_t::second_type; if constexpr(std::is_same_v && std::is_same_v) { other.emplace(map(pair.first), map(pair.second)); } else if constexpr(std::is_same_v) { other.emplace(map(pair.first), std::move(pair.second)); } else { static_assert(std::is_same_v, "Neither the key nor the value are of entity type"); other.emplace(std::move(pair.first), map(pair.second)); } } using std::swap; swap(container, other); } template auto update(char, Container &container) -> decltype(typename Container::value_type{}, void()) { // vector like container static_assert(std::is_same_v, "Invalid value type"); for(auto &&entt: container) { entt = map(entt); } } template void update([[maybe_unused]] Component &instance, [[maybe_unused]] Member Other::*member) { if constexpr(!std::is_same_v) { return; } else if constexpr(std::is_same_v) { instance.*member = map(instance.*member); } else { // maybe a container? let's try... update(0, instance.*member); } } public: /*! Basic registry type. */ using registry_type = Registry; /*! @brief Underlying entity identifier. */ using entity_type = typename registry_type::entity_type; /** * @brief Constructs an instance that is bound to a given registry. * @param source A valid reference to a registry. */ basic_continuous_loader(registry_type &source) noexcept : remloc{source.get_allocator()}, reg{&source} {} /*! @brief Default copy constructor, deleted on purpose. */ basic_continuous_loader(const basic_continuous_loader &) = delete; /*! @brief Default move constructor. */ basic_continuous_loader(basic_continuous_loader &&) noexcept = default; /*! @brief Default destructor. */ ~basic_continuous_loader() noexcept = default; /** * @brief Default copy assignment operator, deleted on purpose. * @return This loader. */ basic_continuous_loader &operator=(const basic_continuous_loader &) = delete; /** * @brief Default move assignment operator. * @return This loader. */ basic_continuous_loader &operator=(basic_continuous_loader &&) noexcept = default; /** * @brief Restores all elements of a type with associated identifiers. * * It creates local counterparts for remote elements as needed.
* Members are either data members of type entity_type or containers of * entities. In both cases, a loader visits them and replaces entities with * their local counterpart. * * @tparam Type Type of elements to restore. * @tparam Archive Type of input archive. * @param archive A valid reference to an input archive. * @param id Optional name used to map the storage within the registry. * @return A valid loader to continue restoring data. */ template basic_continuous_loader &get(Archive &archive, const id_type id = type_hash::value()) { auto &storage = reg->template storage(id); typename traits_type::entity_type length{}; entity_type entt{null}; archive(length); if constexpr(std::is_same_v) { typename traits_type::entity_type in_use{}; storage.reserve(length); archive(in_use); for(std::size_t pos{}; pos < in_use; ++pos) { archive(entt); restore(entt); } for(std::size_t pos = in_use; pos < length; ++pos) { archive(entt); if(const auto entity = to_entity(entt); remloc.contains(entity)) { if(reg->valid(remloc[entity].second)) { reg->destroy(remloc[entity].second); } remloc.erase(entity); } } } else { for(auto &&ref: remloc) { storage.remove(ref.second.second); } while(length--) { if(archive(entt); entt != null) { restore(entt); if constexpr(std::tuple_size_v == 0u) { storage.emplace(map(entt)); } else { Type elem{}; archive(elem); storage.emplace(map(entt), std::move(elem)); } } } } return *this; } /** * @brief Destroys those entities that have no elements. * * In case all the entities were serialized but only part of the elements * was saved, it could happen that some of the entities have no elements * once restored.
* This function helps to identify and destroy those entities. * * @return A non-const reference to this loader. */ basic_continuous_loader &orphans() { internal::orphans(*reg); return *this; } /** * @brief Tests if a loader knows about a given entity. * @param entt A valid identifier. * @return True if `entity` is managed by the loader, false otherwise. */ [[nodiscard]] bool contains(entity_type entt) const noexcept { const auto it = remloc.find(to_entity(entt)); return it != remloc.cend() && it->second.first == entt; } /** * @brief Returns the identifier to which an entity refers. * @param entt A valid identifier. * @return The local identifier if any, the null entity otherwise. */ [[nodiscard]] entity_type map(entity_type entt) const noexcept { if(const auto it = remloc.find(to_entity(entt)); it != remloc.cend() && it->second.first == entt) { return it->second.second; } return null; } private: dense_map> remloc; registry_type *reg; }; } // namespace entt #endif