Newer
Older
Import / projects / Gameloft / core / Utils / Serializer.h
#pragma once

// DOCUMENTATION: https://confluence.gameloft.org/display/OT2/OT+-+Framework%3A+Serialization

#include <RKJson.h>
#include <stack>
#include <string>
#include <type_traits>

// Base class for Json IO serializers.
// This only exists so that we can cram writer and reader together in our API as we did in Zombie
class JsonSerializerBase
{
public:
  virtual bool IsWriter() const = 0;
  virtual bool PushFrame(const char* name) = 0;
  virtual bool PushFrame(size_t index) = 0;
  virtual void PopFrame() = 0;

  virtual bool BeginRootObject() { return true; }
  virtual bool EndRootObject() { return true; }

  // #TODO can probably use root object here as well?
  virtual bool BeginNestedObject() { return BeginRootObject(); }
  virtual bool EndNestedObject() { return EndRootObject(); }

  virtual void MemberProcessed(std::string&& tag, bool successfully) {}

  enum SerializerError
  {
    Error_TypeMismatch,
    Error_MissingMember,  // Member missing in the data
    Error_ExtraMember,    // Member missing in the code
    Error_Unknown,
  };

  struct ErrorRecord
  {
    SerializerError Type;
    std::string Details;
  };

  void RecordError(SerializerError type, std::string&& details)
  {
    m_Errors.emplace_back(ErrorRecord { type, std::move(details) });
  }
  bool HasErrors() const { return !m_Errors.empty(); }
  const std::vector<ErrorRecord>& GetErrors() const { return m_Errors; }

  // #TODO OM: this is just a quick solution to get us up and running, revisit this
  struct Metadata {};
  JsonSerializerBase(Metadata* metadata) : m_Metadata(metadata) {}

  template <class T>
  T* GetMetadata() { return static_cast<T*>(m_Metadata); }

private:
  std::vector<ErrorRecord> m_Errors;

  Metadata* m_Metadata;
};

// Serializer to write values into Json format
class JsonWriter : public JsonSerializerBase
{
public:
  JsonWriter(bne::JsonValueRef json, JsonSerializerBase::Metadata* metadata = nullptr)
    : JsonSerializerBase(metadata)
  {
    m_Frames.push(json);
  }

  bool IsWriter() const override { return true; }

  bne::JsonValueRef CurrentFrame()
  {
    return m_Frames.top();
  }

  bool PushFrame(const char* name) override
  {
    bne::JsonValueRef frame = CurrentFrame()[name];
    m_Frames.push(bne::JsonValueRef::null);
    m_Frames.top().Assign(frame);

    return true;
  }

  bool PushFrame(size_t index) override
  {
    assert(CurrentFrame().isArray());
    bne::JsonValueRef frame = CurrentFrame().appendEmpty();
    m_Frames.push(bne::JsonValueRef::null);
    m_Frames.top().Assign(frame);

    return true;
  }

  void PopFrame() override
  {
    m_Frames.pop();
  }

private:
  std::stack<bne::JsonValueRef> m_Frames;
};

// Serializer to read values from Json format
class JsonReader : public JsonSerializerBase
{
public:
  JsonReader(bne::JsonValueConstRef json, JsonSerializerBase::Metadata* metadata = nullptr, bool enableMigration = true)
    : JsonSerializerBase(metadata), m_EnableMigration(enableMigration)
  {
    m_Frames.push(json);
  }
  
  bool IsWriter() const override { return false; }

  bne::JsonValueConstRef CurrentFrame()
  {
    return m_Frames.top();
  }

  bool PushFrame(const char* name) override
  {
    if (CurrentFrame().isObject())
    {
      if (CurrentFrame().hasMember(name))
      {
        bne::JsonValueConstRef frame = CurrentFrame()[name];
        m_Frames.push(bne::JsonValueConstRef::null);
        m_Frames.top().Assign(frame);

        return true;
      }
      else
      {
        RecordError(JsonSerializerBase::Error_MissingMember, "Requested member could not be found: " + std::string(name));
        return false;
      }
    }
    else
    {
      RecordError(JsonSerializerBase::Error_TypeMismatch, "Expected an object for member: " + std::string(name));
      return false;
    }
  }

  bool PushFrame(size_t index) override
  {
    if (CurrentFrame().isArray())
    {
      if (CurrentFrame().size() > index)
      {
        bne::JsonValueConstRef frame = CurrentFrame()[index];
        m_Frames.push(bne::JsonValueConstRef::null);
        m_Frames.top().Assign(frame);

        return true;
      }
      else
      {
        RecordError(JsonSerializerBase::Error_Unknown, "Invalid array index: " + std::to_string(index));
        return false;
      }
    }
    else
    {
      RecordError(JsonSerializerBase::Error_TypeMismatch, "Expected an array");
      return false;
    }
  }

  void PopFrame() override
  {
    m_Frames.pop();
  }

  bool BeginRootObject() override
  {
    bne::JsonValueConstRef root = CurrentFrame();
    if (!root.isObject())
    {
      RecordError(JsonSerializerBase::Error_TypeMismatch, "Expected an object");
      return false;
    }

    if (IsMigrationEnabled())
    {
      MigrationInfoFrame frame;
      frame.OriginalMembers = root.getMemberNames();

      m_MigrationFrames.push(std::move(frame));
    }

    return true;
  }

  bool EndRootObject() override
  {
    if (IsMigrationEnabled())
    {
      MigrationInfoFrame& currentFrame = m_MigrationFrames.top();
      bool success = true;
      if (!currentFrame.OriginalMembers.empty())
      {
        std::string err("The following members were not consumed by serialization: ");
        for (std::string& mem : currentFrame.OriginalMembers)
        {
          err += mem + ", ";
        }
        RecordError(JsonSerializerBase::Error_ExtraMember, std::move(err));
        success = false;
      }
      if (!currentFrame.NewMembers.empty())
      {
        std::string err("The following new members were not migrated by serialization: ");
        for (std::string& mem : currentFrame.NewMembers)
        {
          err += mem + ", ";
        }
        RecordError(JsonSerializerBase::Error_MissingMember, std::move(err));
        success = false;
      }

      m_MigrationFrames.pop();
      return success;
    }

    return true;
  }

  void MemberProcessed(std::string&& tag, bool successfully) override
  {
    if (IsMigrationEnabled())
    {
      MigrationInfoFrame& currentFrame = m_MigrationFrames.top();
      auto it = std::find(currentFrame.OriginalMembers.begin(), currentFrame.OriginalMembers.end(), tag); // NOLINT(c++11/auto)

      if (successfully)
      {
        // If the field was successfully read, we can remove it
        assert(it != currentFrame.OriginalMembers.end());
        if ((it != currentFrame.OriginalMembers.end()))
        {
          currentFrame.OriginalMembers.erase(it);
        }
      }
      else
      {
        // There might have been a general error reading the value, or this might be a brand new field that doesn't even
        // exist in the data. In any case, migration will have to deal with it
        if ((it == currentFrame.OriginalMembers.end()))
        {
          currentFrame.NewMembers.emplace_back(std::move(tag));
        }
      }
    }
  }

  void MigrateWaiveExistingMember(std::string&& tag)
  {
    if (IsMigrationEnabled())
    {
      MigrationInfoFrame& currentFrame = m_MigrationFrames.top();
      auto it = std::find(currentFrame.OriginalMembers.begin(), currentFrame.OriginalMembers.end(), tag); // NOLINT(c++11/auto)
      if (it != currentFrame.OriginalMembers.end())
      {
        currentFrame.OriginalMembers.erase(it);
      }
    }
  }

  void MigrateWaiveNewMember(std::string&& tag)
  {
    if (IsMigrationEnabled())
    {
      MigrationInfoFrame& currentFrame = m_MigrationFrames.top();
      auto it = std::find(currentFrame.NewMembers.begin(), currentFrame.NewMembers.end(), tag); // NOLINT(c++11/auto)
      if (it != currentFrame.NewMembers.end())
      {
        currentFrame.NewMembers.erase(it);
      }
    }
  }

  bool IsMigrationEnabled() const { return m_EnableMigration; }

private:
  std::stack<bne::JsonValueConstRef> m_Frames;
  bool m_EnableMigration = false;

  struct MigrationInfoFrame
  {
    std::vector<std::string> OriginalMembers;
    std::vector<std::string> NewMembers;
  };
  std::stack<MigrationInfoFrame> m_MigrationFrames;
};

// Base for all serialized objects
class SerializedBase
{
public:
  bool Serialize(JsonSerializerBase& serializer);

protected:
  virtual bool SerializeInternal(JsonSerializerBase& serializer) { return true; }
};

// Function to handle non-class object serialization
template <typename T>
bool JsonSerialize(T& value, JsonSerializerBase& serializer);

// #TODO OM: make this work for transparency's sake?
//bool JsonSerialize(SerializedBase*& value, JsonSerializerBase& serializer);

// For unknown types, we'll first try to serialize as a nested SerializedBase instance
template <typename T, bool isSerializedBase>
struct UnknownTypeSerializer
{
  static_assert(std::is_base_of<SerializedBase, T>::value, "Serialization not implemented for this type!");

  static bool Write(const T& value, JsonWriter& writer)
  {    
    const SerializedBase& serialized = value;
    return const_cast<SerializedBase&>(serialized).Serialize(writer); // #TODO OM: remove the const_cast
  }

  static bool Read(T& value, JsonReader& reader)
  {
    SerializedBase& serialized = value;
    return serialized.Serialize(reader);
  }
};

// As a last resort, we'll try to look for a POD serialization function and let the linker fail if it cannot be found
template <typename T>
struct UnknownTypeSerializer<T, false>
{
  static bool Write(const T& value, JsonWriter& writer)
  {
    return JsonSerialize(const_cast<T&>(value), writer); // #TODO OM: remove the const_cast
  }

  static bool Read(T& value, JsonReader& reader)
  {
    return JsonSerialize(value, reader);
  }
};

// Generic value serializer acts as a fallback option for unknown types
// It will try to serialize the type as SerializedBase if possible, otherwise a compile-time error
// will be emitted
template <typename T>
struct ValueSerializer
{
  static bool Write(const T& value, JsonWriter& writer)
  {
    return UnknownTypeSerializer<T, std::is_base_of<SerializedBase, T>::value>::Write(value, writer);
  }

  static bool Read(T& value, JsonReader& reader)
  {
    return UnknownTypeSerializer<T, std::is_base_of<SerializedBase, T>::value>::Read(value, reader);
  }
};

// Helper to call the appropriate Read or Write serialization function based on the type of the serializer provided
template <typename T>
struct SerializerDispatch
{
  static bool Process(T& value, const char* tag, JsonSerializerBase& serializer)
  {
    bool success = false;

    if (serializer.IsWriter())
    {
      JsonWriter& writer = static_cast<JsonWriter&>(serializer);
      if (writer.PushFrame(tag))
      {
        success = ValueSerializer<T>::Write(value, writer);
        writer.PopFrame();
      }
    }
    else
    {
      JsonReader& reader = static_cast<JsonReader&>(serializer);
      if (reader.PushFrame(tag))
      {
        success = ValueSerializer<T>::Read(value, reader);
        reader.PopFrame();
      }
    }

    if (!success)
    {
      serializer.RecordError(JsonSerializerBase::Error_Unknown, "Failed serializing member: " + std::string(tag));
    }

    serializer.MemberProcessed(tag, success);
    return success;
  }
};

// Use this to declare serialization in a class
#define JSON_SERIALIZED() \
  protected: \
    bool SerializeInternal(JsonSerializerBase& serializer) override;

// Use this to begin serialization definition in a .cpp
// for classes that derive directly from SerializedBase
#define JSON_SERIALIZE_BASE_CLASS(Type) \
  JSON_SERIALIZE_CLASS_BEGIN(Type, SerializedBase)

// Use this to begin serialization definition in a .cpp
// for classes that derive from children of SerializedBase
#define JSON_SERIALIZE_CLASS(Type, Base) \
  JSON_SERIALIZE_CLASS_BEGIN(Type, Base)

#define JSON_SERIALIZE_CLASS_BEGIN(Type, Base) \
  bool Type::SerializeInternal(JsonSerializerBase& serializer) \
  { \
    Type& _valueToSerialize = *this; \
    static const bool _isStruct = false; \
    bool success = Base::SerializeInternal(serializer);

#define JSON_SERIALIZE_STRUCT(Type) \
  template <> \
  bool JsonSerialize(Type& _valueToSerialize, JsonSerializerBase& serializer) \
  { \
    static const bool _isStruct = true; \
    bool success = serializer.BeginRootObject();

// Use this to end a serialization block previously started with JSON_SERIALIZE_BEGIN()
// In the struct case, is this intentionally ignoring the success variable?
#define JSON_SERIALIZE_END(...) \
    return _isStruct ? serializer.EndRootObject() : success; \
  }

// Use this to serialize a member of a class
#define JSON_SERIALIZE(Member, Tag) success &= SerializerDispatch<decltype(_valueToSerialize.Member)>::Process(_valueToSerialize.Member, Tag, serializer);

// Use this if you want to provide a custom (de)serialization function for a member
// Expected function signature is 'bool Fn(<Member Type> member, const char* tag, JsonSerializerBase& serializer)'
#define JSON_SERIALIZE_CUSTOM(Member, Tag, Fn) \
  { \
    bool _scc = Fn(_valueToSerialize.Member, Tag, serializer); \
    if (!_scc) serializer.RecordError(JsonSerializerBase::Error_Unknown, "User defined serialization function has returned an error"); \
    serializer.MemberProcessed(Tag, _scc); \
    success &= _scc; \
  }

// Use these macros to enclose members that should reside inside a named scope in the serialized result
#define JSON_AREA_BEGIN(Name) \
  if (serializer.PushFrame(Name)) \
  { \
    serializer.MemberProcessed(Name, true); /* #TODO OM: Strictly speaking, we should move this to the END macro, but it works just fine here ATM */ \
    if (serializer.BeginNestedObject()) \
    {

#define JSON_AREA_END() \
      serializer.EndNestedObject(); \
    } \
    serializer.PopFrame(); \
  } \
  else \
  { \
    serializer.RecordError(JsonSerializerBase::Error_Unknown, "Failed to process json area"); \
    success = false; \
  }

// Begin a migration block inside serialization definition
#define JSON_MIGRATE_BEGIN(CurrentVersion) \
  if (serializer.IsWriter()) \
  { \
    static_cast<JsonWriter&>(serializer).CurrentFrame()["_version"] = CurrentVersion; \
  } \
  else \
  { \
    JsonReader& _migrationReader = static_cast<JsonReader&>(serializer); \
    int _dataVersion = 0; /* #TODO OM: CurrentVersion? */ \
    if (_migrationReader.CurrentFrame().hasMember("_version")) \
    { \
      _dataVersion = _migrationReader.CurrentFrame()["_version"].asInt(); \
      _migrationReader.MigrateWaiveExistingMember("_version"); \
    } \
    if (CurrentVersion != _dataVersion && _migrationReader.IsMigrationEnabled()) \
    { \
      bne::JsonValue _migrationJson = _migrationReader.CurrentFrame(); \
      switch (_dataVersion) \
      {

// Begin migration to the given version
#define JSON_MIGRATE_TO_VERSION(Version) \
      case (Version - 1):; // the ';' is there to avoid an empty case warning

#define JSON_MIGRATE_END() \
      } \
    } \
  }

// Migration helpers
// The reader used during migration. Same as static_cast<JsonReader&>(serializer)
#define MG_READER _migrationReader
// Reference to the deserialized target value
#define MG_VALUE _valueToSerialize
// A write-enabled copy of the source data that can be used during migration
#define MG_JSON _migrationJson
// A read-only version of the source data
#define MG_JSON_SRC MG_READER.CurrentFrame()

// Mark a member that was added in the current version as migrated
#define MG_WAIVE_NEW_MEMBER(Tag) MG_READER.MigrateWaiveNewMember(Tag);
// Mark a member that was removed or changed in the current version as migrated
#define MG_WAIVE_FAILED_MEMBER(Tag) MG_READER.MigrateWaiveExistingMember(Tag);
// Helper to set a new member and waive the error in one call
#define MG_NEW_MEMBER(Member, Tag, Value) MG_VALUE.Member = Value; MG_WAIVE_NEW_MEMBER(Tag);

struct JsonStructSizeLock
{
  JsonStructSizeLock(size_t size)
  {
    TotalSize += size;
  }

  static size_t TotalSize;
};

// Structure consistency check
#define JSON_LOCK_OBJECT_SIZE(Type, Size) \
  namespace \
  { \
    template <size_t ExpectedSize = Size, size_t ActualSize = sizeof(Type)> \
    struct Type##_SizeLock : JsonStructSizeLock \
    { \
      Type##_SizeLock() : JsonStructSizeLock(ActualSize) {} \
      static_assert(ActualSize == ExpectedSize, "Locked data type size changed, please bump up the version, implement migration, then update the size in JSON_LOCK_OBJECT_SIZE!"); \
    }; \
    static const Type##_SizeLock<> g_##Type##SizeLock; \
  }


#include "SerializerTypes.inl"