Skip to content

Commit

Permalink
Introduce Serializer.saveEntity method iterating through all entity c…
Browse files Browse the repository at this point in the history
…omponents

Linked: #150
  • Loading branch information
AndreasLrx committed Jun 22, 2024
1 parent f2065b5 commit 36502ba
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 4 deletions.
13 changes: 13 additions & 0 deletions src/ecstasy/registry/Registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,19 @@ namespace ecstasy
///
void runSystems(size_t group, size_t mask);

///
/// @brief Get a reference to the storages instances.
///
/// @return constexpr const Instances<IStorage>& Const reference to the storages instance.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
constexpr const Instances<IStorage> &getStorages() const
{
return _storages;
}

private:
Instances<ResourceBase> _resources;
Instances<IStorage> _storages;
Expand Down
2 changes: 2 additions & 0 deletions src/ecstasy/serialization/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ set(SRCROOT ${SRCROOT}/serialization)

set(SRC
${SRC}
${INCROOT}/ComponentSerializer.hpp
${INCROOT}/include.hpp
${INCROOT}/is_serializable.hpp
${INCROOT}/ISerializer.hpp
${INCROOT}/RawSerializer.hpp
${INCROOT}/Serializer.hpp
Expand Down
125 changes: 125 additions & 0 deletions src/ecstasy/serialization/ComponentSerializer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
///
/// @file ComponentSerializers.hpp
/// @author Andréas Leroux ([email protected])
/// @brief
/// @version 1.0.0
/// @date 2022-10-19
///
/// @copyright Copyright (c) ECSTASY 2022 - 2024
///
///

#ifndef ECSTASY_SERIALIZATION_COMPONENT_SERIALIZERS_HPP_
#define ECSTASY_SERIALIZATION_COMPONENT_SERIALIZERS_HPP_

#include "ecstasy/config.hpp"
#include "ecstasy/serialization/RawSerializer.hpp"
#include "util/meta/Traits.hpp"
#include "ecstasy/serialization/is_serializable.hpp"

#ifndef ECSTASY_SERIALIZERS
#define ECSTASY_SERIALIZERS util::meta::Traits<ecstasy::serialization::RawSerializer>
#endif

namespace ecstasy::serialization
{
class ISerializer;

using Serializers = ECSTASY_SERIALIZERS;

///
/// @brief Serialize a component with the given serializer. Serializer is passed as an ISerializer but with its
/// type_info.
///
/// @note This struct is used to counter the limit of the C++ language that doesn't allow to have a virtual
/// template.
///
/// @tparam S Registered serializer types wrapped inside an @ref util::meta::Traits.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
template <typename S>
struct ComponentSerializer {
static_assert(!std::is_same_v<S, util::meta::Traits<>>, "No serializer registered");
};

/// @copydoc ComponentSerializer
template <typename S1, typename... Ss>
struct ComponentSerializer<util::meta::Traits<S1, Ss...>> {
///
/// @brief Serialize a component with the given serializer.
///
/// @note This function will try to serialize the component with each serializer in the list, stopping when the
/// targeted serializer is found (using @p stype).
///
/// @tparam Comp Component type.
///
/// @param[in] serializer Serializer to use.
/// @param[in] stype Type of the serializer.
/// @param[in] component Component to serialize.
///
/// @return ISerializer& The serializer.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
template <typename Comp>
static ISerializer &serialize(ISerializer &serializer, const std::type_info &stype, const Comp &component)
{
(trySerialize<S1>(serializer, stype, component) || ... || trySerialize<Ss>(serializer, stype, component));
return serializer;
}

///
/// @brief Try to serialize a component with the given serializer. The serialization is done if S is the same as
/// @p stype.
///
/// @tparam S Serializer type.
/// @tparam Comp Component type.
///
/// @param[in] serializer Serializer to use.
/// @param[in] stype Type of the serializer searched.
/// @param[in] component Component to serialize.
///
/// @return bool True if the component was serialized, false otherwise.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
template <typename S, typename Comp>
static bool trySerialize(ISerializer &serializer, const std::type_info &stype, const Comp &component)
{
if constexpr (is_serializable_v<S, Comp>) {
if (stype == typeid(S)) {
dynamic_cast<S &>(serializer).saveEntityComponent(component);
return true;
}
}
return false;
}
};

///
/// @brief Serialize a component with the given serializer if possible.
///
/// @tparam C Component type.
///
/// @param[in] serializer Serializer to use.
/// @param[in] stype Type of the serializer.
/// @param[in] component Component to serialize.
///
/// @return ISerializer& The serializer.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
template <typename C>
ISerializer &serialize(ISerializer &serializer, const std::type_info &stype, const C &component)
{
return ComponentSerializer<Serializers>::serialize(serializer, stype, component);
}

} // namespace ecstasy::serialization

#endif /* !ECSTASY_SERIALIZATION_COMPONENT_SERIALIZERS_HPP_ */
45 changes: 44 additions & 1 deletion src/ecstasy/serialization/Serializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,25 @@ namespace ecstasy::serialization
return object >> inner();
}

///
/// @brief Save an entity component to the serializer. This includes the component type before the component
/// data.
///
/// @tparam C Component type.
///
/// @param[in] component Component to save.
///
/// @return S& Reference to @b this for chain calls.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
template <typename C>
S &saveEntityComponent(const C &component)
{
return inner() << typeid(C) << component;
}

///
/// @brief Save an entity to the serializer with explicit components.
///
Expand All @@ -112,10 +131,34 @@ namespace ecstasy::serialization
S &saveEntity(const RegistryEntity &entity)
{
S &s = inner();
((s << typeid(Cs) << entity.get<Cs>()), ...);
(saveEntityComponent(entity.get<Cs>()), ...);
return s;
}

///
/// @brief Save an entity to the serializer.
///
/// @warning This will try to save all the components of the entity, but not the entity ID.
///
/// @param[in] entity Entity to save.
///
/// @return S& Reference to @b this for chain calls.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
S &saveEntity(RegistryEntity &entity)
{
auto storages = entity.getRegistry().getEntityStorages(entity);

for (auto &storage : storages) {
// We send the typeid of the serializer before loosing the type information (since storage.serialize
// takes an ISerializer)
storage.get().serialize(*this, typeid(S), entity.getIndex());
}
return inner();
}

///
/// @brief Load an object from the serializer.
///
Expand Down
2 changes: 2 additions & 0 deletions src/ecstasy/serialization/include.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
#ifndef ECSTASY_SERIALIZER_HPP_
#define ECSTASY_SERIALIZER_HPP_

#include "ComponentSerializer.hpp"
#include "ISerializer.hpp"
#include "RawSerializer.hpp"
#include "Serializer.hpp"
#include "is_serializable.hpp"

///
/// @brief Namespace regrouping the serialization ecstasy classes.
Expand Down
49 changes: 49 additions & 0 deletions src/ecstasy/serialization/is_serializable.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
///
/// @file ComponentSerializers.hpp
/// @author Andréas Leroux ([email protected])
/// @brief
/// @version 1.0.0
/// @date 2022-10-19
///
/// @copyright Copyright (c) ECSTASY 2022 - 2024
///
///

#ifndef ECSTASY_SERIALIZATION_IS_SERIALIZABLE_HPP_
#define ECSTASY_SERIALIZATION_IS_SERIALIZABLE_HPP_

#include <type_traits>

namespace ecstasy::serialization
{
///
/// @brief Check if a component is serializable with the given serializer.
///
/// @tparam S Serializer type.
/// @tparam C Component type.
/// @tparam typename Placeholder for SFINAE.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
template <typename S, typename C, typename = void>
struct is_serializable : std::false_type {};

/// @copydoc is_serializable
template <typename S, typename C>
struct is_serializable<S, C, std::void_t<decltype(std::declval<C &>() >> std::declval<S &>())>> : std::true_type {};

///
/// @brief Alias for @ref is_serializable::value.
///
/// @tparam S Serializer type.
/// @tparam C Component type.
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
template <typename S, typename C>
bool constexpr is_serializable_v = is_serializable<S, C>::value;

} // namespace ecstasy::serialization

#endif /* !ECSTASY_SERIALIZATION_IS_SERIALIZABLE_HPP_ */
11 changes: 11 additions & 0 deletions src/ecstasy/storages/AStorage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "ecstasy/storages/IStorage.hpp"
#include "util/BitSet.hpp"

#include "ecstasy/serialization/ComponentSerializer.hpp"

namespace ecstasy
{

Expand Down Expand Up @@ -162,6 +164,15 @@ namespace ecstasy
{
return (*this)[index];
};

/// @copybrief serialize
virtual serialization::ISerializer &serialize(
serialization::ISerializer &serializer, const std::type_info &stype, size_t entityId) const override final
{
if (contains(entityId))
return serialization::serialize(serializer, stype, at(entityId));
return serializer;
}
};

} // namespace ecstasy
Expand Down
23 changes: 23 additions & 0 deletions src/ecstasy/storages/IStorage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <span>

#include "ecstasy/config.hpp"
#include "util/meta/Traits.hpp"

#ifdef ECSTASY_LOCKABLE_STORAGES
#include "ecstasy/thread/SharedRecursiveMutex.hpp"
Expand All @@ -27,6 +28,11 @@ namespace util

namespace ecstasy
{

namespace serialization
{
class ISerializer;
}
class Entity;

///
Expand Down Expand Up @@ -77,6 +83,23 @@ namespace ecstasy
/// @since 1.0.0 (2022-10-19)
///
virtual bool contains(size_t index) const noexcept = 0;

///
/// @brief Serialize an entity component.
///
/// @note The type_info is used to counter the impossibility to use template virtual methods.
///
/// @param[in] serializer Serializer object.
/// @param[in] stype Type informations of the serializer.
/// @param[in] entityId Entity index.
///
/// @return serialization::ISerializer& Serializer object.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-06-11)
///
virtual serialization::ISerializer &serialize(
serialization::ISerializer &serializer, const std::type_info &stype, size_t entityId) const = 0;
};

} // namespace ecstasy
Expand Down
11 changes: 8 additions & 3 deletions tests/serialization/tests_Serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,17 @@ TEST(Serializer, entityComponents)
registry.entityBuilder().with<Position>(1.0f, -8456.0f).with<NPC>(Position(42.f, 0.f), "Steve").build(),
registry);

rawSerializer.saveEntity<Position, NPC>(entity);
std::string entitySerialized = rawSerializer.getStream().str();
rawSerializer.saveEntity<NPC, Position>(entity);
std::string entitySerializedExplicit = rawSerializer.getStream().str();

rawSerializer.getStream().str("");
rawSerializer << typeid(Position) << entity.get<Position>() << typeid(NPC) << entity.get<NPC>();
rawSerializer << typeid(NPC) << entity.get<NPC>() << typeid(Position) << entity.get<Position>();
std::string expected = rawSerializer.getStream().str();
GTEST_ASSERT_EQ(entitySerializedExplicit, expected);

rawSerializer.getStream().str("");
rawSerializer.saveEntity(entity);
std::string entitySerialized = rawSerializer.getStream().str();
// Not a good test since the order of the components is not guaranteed
GTEST_ASSERT_EQ(entitySerialized, expected);
}

0 comments on commit 36502ba

Please sign in to comment.