Program Listing for File custom_types.hpp

Return to documentation for file (/tmp/ws/src/data_tamer/data_tamer_cpp/include/data_tamer/custom_types.hpp)

#pragma once

#include <mutex>
#include <optional>

#include "data_tamer/types.hpp"
#include "data_tamer/contrib/SerializeMe.hpp"

namespace DataTamer
{

template <class T>
struct TypeDefinition
{
  std::string typeName() const;

  template <class Function>
  void typeDef(const T& obj, Function& addField);
};

class CustomSerializer
{
public:
  using Ptr = std::shared_ptr<CustomSerializer>;

  virtual ~CustomSerializer() = default;
  // name of the type, to be written in the schema string.
  virtual const std::string& typeName() const = 0;

  // optional custom schema of the type
  virtual std::optional<CustomSchema> typeSchema() const { return std::nullopt; }
  // size in bytes of the serialized object.
  // Needed to pre-allocate memory in the buffer
  virtual size_t serializedSize(const void* instance) const = 0;

  // true if the method serializedSize will ALWAYS return the same value
  virtual bool isFixedSize() const = 0;

  // serialize an object into a buffer.
  virtual void serialize(const void* instance, SerializeMe::SpanBytes&) const = 0;
};

//------------------------------------------------------------------

// This derived class is used automatically by all the types
// that have a template specialization of TypeDefinition<T>
template <typename T>
class CustomSerializerT : public CustomSerializer
{
public:
  CustomSerializerT(const std::string& type_name = TypeDefinition<T>().typeName());

  const std::string& typeName() const override;

  size_t serializedSize(const void* src_instance) const override;

  bool isFixedSize() const override;

  void serialize(const void* src_instance,
                 SerializeMe::SpanBytes& dst_buffer) const override;

private:
  std::string _name;
  size_t _fixed_size = 0;
};

class TypesRegistry
{
public:
  template <typename T>
  CustomSerializer::Ptr addType(const std::string& type_name,
                                bool skip_if_present = false);

  template <typename T>
  [[nodiscard]] CustomSerializer::Ptr getSerializer();

private:
  std::unordered_map<std::string, CustomSerializer::Ptr> _types;
  std::recursive_mutex _mutex;
};

//------------------------------------------------------------------
//------------------------------------------------------------------
//------------------------------------------------------------------

template <template <class, class> class Container, class T, class... TArgs>
struct TypeDefinition<Container<T, TArgs...>>
{
  std::string typeName() const { return TypeDefinition<T>().typeName(); }
};

template <typename T, size_t N>
struct TypeDefinition<std::array<T, N>>
{
  std::string typeName() const { return TypeDefinition<T>().typeName(); }
};

template <class C, typename T>
T getPointerType(T C::*v);

template <class T, typename = void>
struct has_typedef_with_object : std::false_type
{
};

inline void DummyAddField(const char*, const void*){}
using EmptyFunc = decltype(DummyAddField);

template <class T>
struct has_typedef_with_object<T,
                               decltype(TypeDefinition<T>().typeDef(
                                   std::declval<const T&>(), std::declval<EmptyFunc&>()))>
  : std::true_type
{
};

template <class T, typename = void>
struct has_typedef_with_member : std::false_type
{
};

template <class T>
struct has_typedef_with_member<T, decltype(TypeDefinition<T>().typeDef(
                                      std::declval<EmptyFunc&>()))> : std::true_type
{
};

// Recursive function to compute if a type has fixed size (at compile time).
// Used mainly by the CustomSerializerT constructor.
template <typename T>
inline void GetFixedSize(bool& is_fixed_size, size_t& fixed_size)
{
  if constexpr (IsNumericType<T>())
  {
    fixed_size += sizeof(T);
  }
  else
  {
    constexpr auto info = SerializeMe::container_info<T>();
    if constexpr (info.is_container && info.size == 0)
    {
      // vector
      is_fixed_size = false;
    }
    else if constexpr (info.is_container && info.size >= 0)
    {
      // array
      size_t obj_size;
      using Type = typename SerializeMe::container_info<T>::value_type;
      GetFixedSize<Type>(is_fixed_size, obj_size);
      fixed_size += info.size * obj_size;
    }
    else if (is_fixed_size)
    {
      if constexpr (has_typedef_with_object<T>())
      {
        auto funcA = [&](const char*, auto const* member) {
          using MemberType = std::remove_cv_t<std::remove_reference_t<decltype(*member)>>;
          GetFixedSize<MemberType>(is_fixed_size, fixed_size);
        };
        TypeDefinition<T>().typeDef({}, funcA);
      }
      else
      {
        auto funcB = [&](const char*, auto const& member) {
          using MemberType = decltype(getPointerType(member));
          GetFixedSize<MemberType>(is_fixed_size, fixed_size);
        };
        TypeDefinition<T>().typeDef(funcB);
      }
    }
  }
}

template <typename T>
inline CustomSerializerT<T>::CustomSerializerT(const std::string& type_name) :
  _name(type_name)
{
  static_assert(!SerializeMe::container_info<T>().is_container, "Don't pass containers a "
                                                                "template type");

  bool is_fixed_size = true;
  GetFixedSize<T>(is_fixed_size, _fixed_size);
  if (!is_fixed_size)
  {
    _fixed_size = 0;
  }
}

template <typename T>
inline const std::string& CustomSerializerT<T>::typeName() const
{
  return _name;
}

template <typename T>
inline size_t CustomSerializerT<T>::serializedSize(const void* src_instance) const
{
  if (_fixed_size != 0)
  {
    return _fixed_size;
  }
  const auto* obj = static_cast<const T*>(src_instance);
  return SerializeMe::BufferSize(*obj);
}

template <typename T>
inline bool CustomSerializerT<T>::isFixedSize() const
{
  return _fixed_size > 0;
}

template <typename T>
inline void CustomSerializerT<T>::serialize(const void* src_instance,
                                            SerializeMe::SpanBytes& dst_buffer) const
{
  const auto* obj = static_cast<const T*>(src_instance);
  SerializeMe::SerializeIntoBuffer(dst_buffer, *obj);
}

template <typename T>
inline CustomSerializer::Ptr TypesRegistry::getSerializer()
{
  static_assert(!IsNumericType<T>(), "You don't need to create a serializer for a "
                                     "numerical type.");
  static_assert(!SerializeMe::container_info<T>().is_container, "Don't pass containers "
                                                                "as template type");

  std::scoped_lock lk(_mutex);
  const auto type_name = TypeDefinition<T>().typeName();
  auto it = _types.find(type_name);

  if (it == _types.end())
  {
    CustomSerializer::Ptr serializer = std::make_shared<CustomSerializerT<T>>(type_name);
    _types[type_name] = serializer;
    return serializer;
  }
  return it->second;
}

template <typename T>
inline CustomSerializer::Ptr TypesRegistry::addType(const std::string& type_name,
                                                    bool skip_if_present)
{
  static_assert(!IsNumericType<T>(), "You don't need to create a serializer for a "
                                     "numerical type.");
  static_assert(!SerializeMe::container_info<T>().is_container, "Don't pass containers "
                                                                "as template type");

  std::scoped_lock lk(_mutex);
  if (skip_if_present && _types.count(type_name) != 0)
  {
    return {};
  }
  CustomSerializer::Ptr serializer = std::make_shared<CustomSerializerT<T>>(type_name);
  _types[type_name] = serializer;
  return serializer;
}

}   // namespace DataTamer

// namespace wrapping
// Forward DataTamer::TypeDefinition to SerializeMe::TypeDefinition
namespace SerializeMe
{

template <class T>
inline std::string TypeDefinition<T>::typeName() const
{
  return DataTamer::TypeDefinition<T>().typeName();
}

template <class T>
template <class Function>
void TypeDefinition<T>::typeDef(const T& obj, Function& addField)
{
  if constexpr (DataTamer::has_typedef_with_object<T>())
  {
    DataTamer::TypeDefinition<T>().typeDef(obj, addField);
  }
  else
  {
    auto func = [&obj, &addField](const char* name, const auto& member) {
      addField(name, &(obj.*member));
    };
    DataTamer::TypeDefinition<T>().typeDef(func);
  }
}

}   // namespace SerializeMe