Skip to content

Commit

Permalink
feat(data): add binary serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
RiscadoA committed Sep 10, 2024
1 parent 4c2d6a4 commit 5b69b2a
Show file tree
Hide file tree
Showing 8 changed files with 466 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- PhysicsMaterial component (#1254, **@fallenatlas**).
- Components to support rigid body rotation (#865, **@fallenatlas**).
- Compute contact points and contact manifold for collision between boxes (#1243, **@fallenatlas**).
- Binary Serializer and Deserializer (#1306, **@RiscadoA**).

## [v0.3.0] - 2024-08-02

Expand Down
2 changes: 2 additions & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ set(CUBOS_CORE_SOURCE
"src/data/ser/serializer.cpp"
"src/data/ser/json.cpp"
"src/data/ser/debug.cpp"
"src/data/ser/binary.cpp"
"src/data/des/deserializer.cpp"
"src/data/des/json.cpp"
"src/data/des/binary.cpp"

"src/io/window.cpp"
"src/io/cursor.cpp"
Expand Down
27 changes: 27 additions & 0 deletions core/include/cubos/core/data/des/binary.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// @file
/// @brief Class @ref cubos::core::data::BinaryDeserializer.
/// @ingroup core-data-des

#pragma once

#include <cubos/core/data/des/deserializer.hpp>
#include <cubos/core/memory/stream.hpp>

namespace cubos::core::data
{
/// @brief Implementation of the abstract Deserializer class meant to deserialize data written by a @ref
/// BinarySerializer.
class CUBOS_CORE_API BinaryDeserializer : public Deserializer
{
public:
/// @brief Constructs.
/// @param stream Stream to read from.
BinaryDeserializer(memory::Stream& stream);

protected:
bool decompose(const reflection::Type& type, void* value) override;

private:
memory::Stream& mStream; ///< Stream to deserialize from.
};
} // namespace cubos::core::data
27 changes: 27 additions & 0 deletions core/include/cubos/core/data/ser/binary.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// @file
/// @brief Class @ref cubos::core::data::BinarySerializer.
/// @ingroup core-data-ser

#pragma once

#include <cubos/core/data/ser/serializer.hpp>
#include <cubos/core/memory/stream.hpp>

namespace cubos::core::data
{
/// @brief Implementation of the abstract Serializer class meant to serialize data in a non-readable but fast and
/// compact way.
class CUBOS_CORE_API BinarySerializer : public Serializer
{
public:
/// @brief Constructs.
/// @param stream Stream to write to.
BinarySerializer(memory::Stream& stream);

protected:
bool decompose(const reflection::Type& type, const void* value) override;

private:
memory::Stream& mStream; ///< Stream to serialize to.
};
} // namespace cubos::core::data
184 changes: 184 additions & 0 deletions core/src/data/des/binary.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#include <cubos/core/data/des/binary.hpp>
#include <cubos/core/log.hpp>
#include <cubos/core/memory/any_value.hpp>
#include <cubos/core/memory/endianness.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/enum.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::BinaryDeserializer;
using cubos::core::memory::AnyValue;
using cubos::core::reflection::ArrayTrait;
using cubos::core::reflection::DictionaryTrait;
using cubos::core::reflection::EnumTrait;
using cubos::core::reflection::FieldsTrait;
using cubos::core::reflection::StringConversionTrait;
using cubos::core::reflection::Type;

// NOLINTBEGIN(bugprone-macro-parentheses)
#define AUTO_HOOK(casted, type) \
this->hook<type>([this](type& value) { \
casted castedValue; \
if (!mStream.readExact(&castedValue, sizeof(castedValue))) \
{ \
return false; \
} \
value = static_cast<type>(memory::fromLittleEndian(castedValue)); \
return true; \
})
// NOLINTEND(bugprone-macro-parentheses)

BinaryDeserializer::BinaryDeserializer(memory::Stream& stream)
: mStream(stream)
{
this->hook<bool>([this](bool& value) {
value = mStream.get() != '\0';
return !mStream.eof();
});
this->hook<char>([this](char& value) { return mStream.readExact(&value, 1); });

AUTO_HOOK(std::int8_t, signed char);
AUTO_HOOK(std::int16_t, short);
AUTO_HOOK(std::int32_t, int);
AUTO_HOOK(std::int64_t, long);
AUTO_HOOK(std::int64_t, long long);

AUTO_HOOK(std::uint8_t, unsigned char);
AUTO_HOOK(std::uint16_t, unsigned short);
AUTO_HOOK(std::uint32_t, unsigned int);
AUTO_HOOK(std::uint64_t, unsigned long);
AUTO_HOOK(std::uint64_t, unsigned long long);

AUTO_HOOK(float, float);
AUTO_HOOK(double, double);

this->hook<std::string>([this](std::string& value) {
std::size_t length;
if (!this->read(length))
{
return false;

Check warning on line 64 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L64

Added line #L64 was not covered by tests
}

value.resize(length);
return mStream.readExact(value.data(), length);
});
}

bool BinaryDeserializer::decompose(const Type& type, void* value)
{
if (type.has<DictionaryTrait>())
{
const auto& trait = type.get<DictionaryTrait>();
auto view = trait.view(value);
view.clear();

std::size_t length;
if (!this->read(length))
{
CUBOS_ERROR("Could not deserialize dictionary length");
return false;

Check warning on line 84 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L83-L84

Added lines #L83 - L84 were not covered by tests
}

auto key = AnyValue::defaultConstruct(trait.keyType());
for (std::size_t i = 0; i < length; ++i)
{
if (!this->read(trait.keyType(), key.get()))
{
CUBOS_ERROR("Could not deserialize dictionary key");
return false;

Check warning on line 93 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L92-L93

Added lines #L92 - L93 were not covered by tests
}

view.insertDefault(key.get());
auto entry = view.find(key.get());
CUBOS_ASSERT(entry != view.end(), "We just inserted the entry, where did it go?");

if (!this->read(trait.valueType(), entry->value))
{
CUBOS_ERROR("Could not deserialize dictionary value");
return false;

Check warning on line 103 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L102-L103

Added lines #L102 - L103 were not covered by tests
}
}
}
else if (type.has<ArrayTrait>())
{
const auto& trait = type.get<ArrayTrait>();
auto view = trait.view(value);

std::size_t length;
if (!this->read(length))
{
CUBOS_ERROR("Could not deserialize array length");
return false;

Check warning on line 116 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L115-L116

Added lines #L115 - L116 were not covered by tests
}
view.resize(length);

for (auto* element : view)
{
if (!this->read(trait.elementType(), element))
{
CUBOS_ERROR("Could not deserialize array element");
return false;

Check warning on line 125 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L124-L125

Added lines #L124 - L125 were not covered by tests
}
}
}
else if (type.has<EnumTrait>())
{
const auto& trait = type.get<EnumTrait>();

std::string name;
if (!this->read(name))
{
CUBOS_ERROR("Could not deserialize enum");
return false;

Check warning on line 137 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L136-L137

Added lines #L136 - L137 were not covered by tests
}

if (!trait.contains(name))
{
CUBOS_ERROR("Could not deserialize enum, no such variant '{}'", name);
return false;

Check warning on line 143 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L142-L143

Added lines #L142 - L143 were not covered by tests
}

trait.at(name).set(value);
}
else if (type.has<FieldsTrait>())
{
const auto& trait = type.get<FieldsTrait>();
for (const auto& [field, fieldValue] : trait.view(value))
{
if (!this->read(field->type(), fieldValue))
{
CUBOS_ERROR("Could not deserialize field '{}'", field->name());
return false;

Check warning on line 156 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L155-L156

Added lines #L155 - L156 were not covered by tests
}
}
}
else if (type.has<StringConversionTrait>())
{
const auto& trait = type.get<StringConversionTrait>();

std::string string;
if (!this->read(string))
{
CUBOS_ERROR("Could not deserialize string conversion");
return false;

Check warning on line 168 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L167-L168

Added lines #L167 - L168 were not covered by tests
}

if (!trait.from(value, string))
{
CUBOS_ERROR("Could not deserialize string conversion, string '{}' is not a valid conversion", string);
return false;

Check warning on line 174 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L173-L174

Added lines #L173 - L174 were not covered by tests
}
}
else
{
CUBOS_WARN("Type {} doesn't have any of the supported traits", type.name());
return false;

Check warning on line 180 in core/src/data/des/binary.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/data/des/binary.cpp#L179-L180

Added lines #L179 - L180 were not covered by tests
}

return true;
}
Loading

0 comments on commit 5b69b2a

Please sign in to comment.