-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(serialization): add DebugSerializer
- Loading branch information
Showing
5 changed files
with
306 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/// @file | ||
/// @brief Class @ref cubos::core::data::DebugSerializer. | ||
/// @ingroup core-data-ser | ||
|
||
#pragma once | ||
|
||
#include <cubos/core/data/ser/serializer.hpp> | ||
#include <cubos/core/memory/stream.hpp> | ||
|
||
namespace cubos::core::data | ||
{ | ||
/// @brief Serializer implementation which prints the given data to a stream in a human-readable | ||
/// format not meant to be parsed. | ||
/// @ingroup core-data-ser | ||
class DebugSerializer : public Serializer | ||
{ | ||
public: | ||
/// @brief Constructs. | ||
/// @param stream Stream to serialize to. | ||
DebugSerializer(memory::Stream& stream); | ||
|
||
/// @brief Sets whether to pretty-print the output. | ||
/// @param pretty Whether to pretty-print the output. | ||
void pretty(bool pretty); | ||
|
||
protected: | ||
bool decompose(const reflection::Type& type, const void* value) override; | ||
|
||
private: | ||
void separate(bool first); | ||
|
||
memory::Stream& mStream; ///< Stream to serialize to. | ||
bool mPretty{false}; ///< Whether to pretty-print the output. | ||
int mLevel{0}; ///< Indentation level. | ||
}; | ||
} // namespace cubos::core::data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
#include <cubos/core/data/ser/debug.hpp> | ||
#include <cubos/core/log.hpp> | ||
#include <cubos/core/reflection/external/primitives.hpp> | ||
#include <cubos/core/reflection/external/string.hpp> | ||
#include <cubos/core/reflection/traits/array.hpp> | ||
#include <cubos/core/reflection/traits/dictionary.hpp> | ||
#include <cubos/core/reflection/traits/fields.hpp> | ||
#include <cubos/core/reflection/traits/string_conversion.hpp> | ||
#include <cubos/core/reflection/type.hpp> | ||
|
||
using cubos::core::data::DebugSerializer; | ||
using cubos::core::reflection::ArrayTrait; | ||
using cubos::core::reflection::DictionaryTrait; | ||
using cubos::core::reflection::FieldsTrait; | ||
using cubos::core::reflection::StringConversionTrait; | ||
using cubos::core::reflection::Type; | ||
|
||
#define AUTO_HOOK(type, ...) \ | ||
this->hook<type>([](Serializer& ser, const Type&, const void* value) { \ | ||
auto& debug = static_cast<DebugSerializer&>(ser); \ | ||
const auto& typed = *static_cast<const type*>(value); \ | ||
__VA_ARGS__; \ | ||
return true; \ | ||
}) | ||
|
||
DebugSerializer::DebugSerializer(memory::Stream& stream) | ||
: mStream(stream) | ||
{ | ||
AUTO_HOOK(bool, debug.mStream.print(typed ? "true" : "false")); | ||
AUTO_HOOK(char, { | ||
debug.mStream.put('\''); | ||
debug.mStream.put(typed); | ||
debug.mStream.put('\''); | ||
}); | ||
AUTO_HOOK(int8_t, debug.mStream.print(typed)); | ||
AUTO_HOOK(int16_t, debug.mStream.print(typed)); | ||
AUTO_HOOK(int32_t, debug.mStream.print(typed)); | ||
AUTO_HOOK(int64_t, debug.mStream.print(typed)); | ||
AUTO_HOOK(uint8_t, debug.mStream.print(typed)); | ||
AUTO_HOOK(uint16_t, debug.mStream.print(typed)); | ||
AUTO_HOOK(uint32_t, debug.mStream.print(typed)); | ||
AUTO_HOOK(uint64_t, debug.mStream.print(typed)); | ||
AUTO_HOOK(float, debug.mStream.print(typed)); | ||
AUTO_HOOK(double, debug.mStream.print(typed)); | ||
AUTO_HOOK(std::string, debug.mStream.printf("\"{}\"", typed)); | ||
} | ||
|
||
void DebugSerializer::pretty(bool pretty) | ||
{ | ||
mPretty = pretty; | ||
} | ||
|
||
void DebugSerializer::separate(bool first) | ||
{ | ||
if (mPretty) | ||
{ | ||
if (!first) | ||
{ | ||
mStream.put(','); | ||
} | ||
|
||
mStream.put('\n'); | ||
for (int i = 0; i < mLevel * 2; ++i) | ||
{ | ||
mStream.put(' '); | ||
} | ||
} | ||
else if (!first) | ||
{ | ||
mStream.print(", "); | ||
} | ||
} | ||
|
||
bool DebugSerializer::decompose(const Type& type, const void* value) | ||
{ | ||
bool success = true; | ||
bool first = true; | ||
|
||
if (type.has<DictionaryTrait>()) | ||
{ | ||
const auto& trait = type.get<DictionaryTrait>(); | ||
|
||
mStream.put('{'); | ||
mLevel += 1; | ||
|
||
for (auto [entryKey, entryValue] : trait.view(value)) | ||
{ | ||
this->separate(first); | ||
first = false; | ||
|
||
if (!this->write(trait.keyType(), entryKey)) | ||
{ | ||
CUBOS_WARN("Could not write dictionary key"); | ||
mStream.put('?'); | ||
success = false; | ||
} | ||
|
||
mStream.print(": "); | ||
|
||
if (!this->write(trait.valueType(), entryValue)) | ||
{ | ||
CUBOS_WARN("Could not write dictionary value"); | ||
mStream.put('?'); | ||
success = false; | ||
} | ||
} | ||
|
||
mLevel -= 1; | ||
this->separate(true); | ||
mStream.put('}'); | ||
} | ||
else if (type.has<ArrayTrait>()) | ||
{ | ||
const auto& trait = type.get<ArrayTrait>(); | ||
|
||
mStream.put('['); | ||
mLevel += 1; | ||
|
||
for (const auto* element : trait.view(value)) | ||
{ | ||
this->separate(first); | ||
first = false; | ||
|
||
if (!this->write(trait.elementType(), element)) | ||
{ | ||
CUBOS_WARN("Could not write array element"); | ||
mStream.put('?'); | ||
success = false; | ||
} | ||
} | ||
|
||
mLevel -= 1; | ||
this->separate(true); | ||
mStream.put(']'); | ||
} | ||
else if (type.has<FieldsTrait>()) | ||
{ | ||
const auto& trait = type.get<FieldsTrait>(); | ||
|
||
mStream.put('('); | ||
mLevel += 1; | ||
|
||
for (const auto& [field, fieldValue] : trait.view(value)) | ||
{ | ||
this->separate(first); | ||
first = false; | ||
|
||
mStream.printf("{}: ", field->name()); | ||
|
||
if (!this->write(field->type(), fieldValue)) | ||
{ | ||
CUBOS_WARN("Could not write field"); | ||
mStream.put('?'); | ||
success = false; | ||
} | ||
} | ||
|
||
mLevel -= 1; | ||
this->separate(true); | ||
mStream.put(')'); | ||
} | ||
else if (type.has<StringConversionTrait>()) | ||
{ | ||
const auto& trait = type.get<StringConversionTrait>(); | ||
mStream.print(trait.into(value)); | ||
} | ||
else | ||
{ | ||
CUBOS_WARN("Type {} doesn't have any of the supported traits", type.name()); | ||
mStream.put('?'); | ||
success = false; | ||
} | ||
|
||
return success; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
#include <doctest/doctest.h> | ||
#include <glm/vec3.hpp> | ||
|
||
#include <cubos/core/data/ser/debug.hpp> | ||
#include <cubos/core/memory/buffer_stream.hpp> | ||
#include <cubos/core/reflection/external/glm.hpp> | ||
#include <cubos/core/reflection/external/map.hpp> | ||
#include <cubos/core/reflection/external/primitives.hpp> | ||
#include <cubos/core/reflection/external/string.hpp> | ||
#include <cubos/core/reflection/external/uuid.hpp> | ||
#include <cubos/core/reflection/external/vector.hpp> | ||
|
||
#include "../utils.hpp" | ||
|
||
using cubos::core::data::DebugSerializer; | ||
using cubos::core::data::Serializer; | ||
using cubos::core::memory::BufferStream; | ||
using cubos::core::memory::SeekOrigin; | ||
using cubos::core::reflection::Type; | ||
|
||
namespace | ||
{ | ||
struct Empty | ||
{ | ||
CUBOS_REFLECT | ||
{ | ||
return Type::create("Empty"); | ||
} | ||
}; | ||
} // namespace | ||
|
||
#define AUTO_TEST(type, value, expected, success) \ | ||
stream.seek(0, SeekOrigin::Begin); \ | ||
CHECK(ser.write<type>(value) == (success)); \ | ||
stream.put('\0'); \ | ||
stream.seek(0, SeekOrigin::Begin); \ | ||
CHECK(dump(stream) == (expected)); | ||
|
||
#define AUTO_TEST_SPLIT(type, value, expectedUgly, expectedPretty, success) \ | ||
if (pretty) \ | ||
{ \ | ||
AUTO_TEST(type, value, expectedPretty, success); \ | ||
} \ | ||
else \ | ||
{ \ | ||
AUTO_TEST(type, value, expectedUgly, success); \ | ||
} | ||
|
||
TEST_CASE("data::DebugSerializer") | ||
{ | ||
BufferStream stream{}; | ||
DebugSerializer ser{stream}; | ||
|
||
bool pretty = false; | ||
PARAMETRIZE_TRUE_OR_FALSE("pretty", pretty); | ||
ser.pretty(pretty); | ||
|
||
AUTO_TEST(bool, false, "false", true); | ||
AUTO_TEST(bool, true, "true", true); | ||
AUTO_TEST(char, 'a', "'a'", true); | ||
AUTO_TEST(int8_t, -120, "-120", true); | ||
AUTO_TEST(int16_t, 30000, "30000", true); | ||
AUTO_TEST(int32_t, -2000000000, "-2000000000", true); | ||
AUTO_TEST(int64_t, 9000000000000000000, "9000000000000000000", true); | ||
AUTO_TEST(uint8_t, 120, "120", true); | ||
AUTO_TEST(uint16_t, 60000, "60000", true); | ||
AUTO_TEST(uint32_t, 4000000000, "4000000000", true); | ||
AUTO_TEST(uint64_t, 18000000000000000000ULL, "18000000000000000000", true); | ||
AUTO_TEST(float, 1.5F, "1.5000", true); | ||
AUTO_TEST(double, 1.F, "1.0000", true); | ||
AUTO_TEST(std::string, "Hello, world!", "\"Hello, world!\"", true); | ||
AUTO_TEST(uuids::uuid, *uuids::uuid::from_string("f7063cd4-de44-47b5-b0a9-f0e7a558c9e5"), | ||
"f7063cd4-de44-47b5-b0a9-f0e7a558c9e5", true); | ||
|
||
std::map<std::string, bool> map{{"false", false}, {"true", true}}; | ||
AUTO_TEST_SPLIT(decltype(map), map, "{\"false\": false, \"true\": true}", | ||
"{\n \"false\": false,\n \"true\": true\n}", true); | ||
|
||
std::vector<int> vector{1, 2, 3}; | ||
AUTO_TEST_SPLIT(decltype(vector), vector, "[1, 2, 3]", "[\n 1,\n 2,\n 3\n]", true); | ||
|
||
glm::tvec3<int> vec3{1, 2, 3}; | ||
AUTO_TEST_SPLIT(decltype(vec3), vec3, "(x: 1, y: 2, z: 3)", "(\n x: 1,\n y: 2,\n z: 3\n)", true); | ||
|
||
ser.hook<std::string>([](Serializer&, const Type&, const void*) { return false; }); | ||
ser.hook<bool>([](Serializer&, const Type&, const void*) { return false; }); | ||
ser.hook<int>([](Serializer&, const Type&, const void*) { return false; }); | ||
AUTO_TEST_SPLIT(decltype(map), map, "{?: ?, ?: ?}", "{\n ?: ?,\n ?: ?\n}", false); | ||
AUTO_TEST_SPLIT(decltype(vector), vector, "[?, ?, ?]", "[\n ?,\n ?,\n ?\n]", false); | ||
AUTO_TEST_SPLIT(decltype(vec3), vec3, "(x: ?, y: ?, z: ?)", "(\n x: ?,\n y: ?,\n z: ?\n)", false); | ||
|
||
AUTO_TEST_SPLIT(Empty, Empty{}, "?", "?", false); | ||
} |