Program Listing for File custom_types.hpp

Return to documentation for file (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
{

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(std::string type_name);

  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 <typename T>
struct CustomTypeName
{
  static constexpr std::string_view get()
  {
    static_assert(std::is_default_constructible_v<T>, "Must be default constructible");
    static_assert(SerializeMe::has_TypeDefinition<T>(), "Missing TypeDefinition");
    T dummy;
    return TypeDefinition(dummy, SerializeMe::EmptyFuncion);
  }
};

template <template <class, class> class Container, class T, class... TArgs>
struct CustomTypeName<Container<T, TArgs...>>
{
  static constexpr std::string_view get() { return CustomTypeName<T>::get(); }
};

template <typename T, size_t N>
struct CustomTypeName<std::array<T, N>>
{
  static constexpr std::string_view get() { return CustomTypeName<T>::get(); }
};

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

// 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)
{
  using namespace SerializeMe;

  if constexpr(IsNumericType<T>())
  {
    fixed_size += sizeof(T);
  }
  else
  {
    constexpr auto info = 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 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_TypeDefinition<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);
        };
        T dummy;
        TypeDefinition(dummy, funcA);
      }
      else
      {
        throw std::logic_error("Missing TypeDefinition");
      }
    }
  }
}

template <typename T>
inline CustomSerializerT<T>::CustomSerializerT(std::string type_name)
  : _name(std::move(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);
  T dummy;
  const std::string type_name(CustomTypeName<T>::get());
  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