begin packet serialization

This commit is contained in:
2024-07-18 15:28:01 +02:00
parent 2e63a474b8
commit 6e9c747b2d
17 changed files with 842 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
#pragma once
/**
* \file Format.h
* \brief This file contains the definition of the `Format` function.
*/
#include <memory>
#include <stdexcept>
#include <string>
namespace blitz {
/**
* \brief Formats a string using a format string and variadic arguments.
*
* This function takes a format string and a variable number of arguments and returns a formatted string.
* The format string can contain placeholders that will be replaced by the corresponding arguments.
*
* \param format The format string.
* \param args The variadic arguments to be formatted.
* \return The formatted string.
* \throws std::runtime_error if an error occurs during formatting.
*/
template <typename... Args>
std::string Format(const std::string& format, Args... args) {
int size = snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
if (size <= 0) {
throw std::runtime_error("Error during formatting.");
}
std::unique_ptr<char[]> buf(new char[size]);
snprintf(buf.get(), static_cast<std::size_t>(size), format.c_str(), args...);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}
} // namespace blitz

View File

@@ -0,0 +1,30 @@
#pragma once
/**
* \file Log.h
* \brief File defining log functions
*/
#include <string>
namespace blitz {
/**
* \brief Logs a normal message.
* \param msg The message to be logged.
*/
void Log(const std::string& msg);
/**
* \brief Logs a normal message in debug mode.
* \param msg The message to be logged.
*/
void LogD(const std::string& msg);
/**
* \brief Logs an error message.
* \param err The error message to be logged.
*/
void LogE(const std::string& err);
} // namespace blitz

View File

@@ -0,0 +1,49 @@
#pragma once
#include <string>
namespace blitz {
namespace protocol {
namespace data {
struct PlayerLogin {
std::string m_PlayerName;
};
struct UpdateHealth {
float m_NewHealth;
};
struct LoggingSuccess {};
struct PlayerDeath {};
struct PlayerJoin {};
struct PlayerLeave {};
struct PlayerStats {};
struct PlayerList {};
struct ServerConfig {};
struct ServerTps {};
struct UpdateGameState {};
struct KeepAlive {};
struct Disconnect {};
struct ChatMessage {
std::string m_Text;
};
struct PlayerPositionAndRotation {};
struct PlayerShoot {};
} // namespace data
} // namespace protocol
} // namespace blitz

View File

@@ -0,0 +1,19 @@
#pragma once
#include <blitz/protocol/Packets.h>
#include <memory>
namespace blitz {
namespace protocol {
namespace PacketFactory {
template<typename PacketDerived, typename = typename std::enable_if<std::is_base_of<Packet, PacketDerived>::value>::type>
std::unique_ptr<PacketDerived> CreatePacket() {
return std::make_unique<PacketDerived>();
}
const std::unique_ptr<Packet>& CreateReadOnlyPacket(PacketType a_Type);
} // namespace PacketFactory
} // namespace protocol
} // namespace blitz

View File

@@ -0,0 +1,19 @@
#pragma once
#include <blitz/protocol/Packets.h>
#include <Nazara/Core/ByteArray.hpp>
namespace blitz {
namespace protocol {
using PacketPtr = std::unique_ptr<Packet>;
namespace PacketSerializer {
Nz::ByteArray Serialize(const Packet& a_Packet);
std::unique_ptr<Packet> Deserialize(Nz::ByteArray& a_Data);
}
} // namespace protocol
} // namespace blitz

View File

@@ -0,0 +1,35 @@
#pragma once
#include <blitz/protocol/Packets.h>
namespace blitz {
namespace protocol {
class PacketVisitor {
protected:
PacketVisitor() {}
virtual ~PacketVisitor() {}
public:
void Check(const Packet& packet);
virtual void Visit(const packets::PlayerLogin&) {}
virtual void Visit(const packets::UpdateHealth&) {}
virtual void Visit(const packets::LoggingSuccess&) {}
virtual void Visit(const packets::PlayerDeath&) {}
virtual void Visit(const packets::PlayerJoin&) {}
virtual void Visit(const packets::PlayerLeave&) {}
virtual void Visit(const packets::PlayerList&) {}
virtual void Visit(const packets::PlayerStats&) {}
virtual void Visit(const packets::ServerConfig&) {}
virtual void Visit(const packets::ServerTps&) {}
virtual void Visit(const packets::UpdateGameState&) {}
virtual void Visit(const packets::KeepAlive&) {}
virtual void Visit(const packets::Disconnect&) {}
virtual void Visit(const packets::ChatMessage&) {}
virtual void Visit(const packets::PlayerPositionAndRotation&) {}
virtual void Visit(const packets::PlayerShoot&) {}
};
} // namespace protocol
} // namespace blitz

View File

@@ -0,0 +1,121 @@
#pragma once
#include <string>
#include <blitz/protocol/PacketData.h>
namespace blitz {
namespace protocol {
class PacketVisitor;
using PacketID = std::uint8_t;
enum class PacketType : PacketID {
// client --> server
PlayerLogin = 0,
UpdateHealth,
// client <-- server
LoggingSuccess,
PlayerDeath,
PlayerJoin,
PlayerLeave,
PlayerList,
PlayerStats,
ServerConfig,
ServerTps,
UpdateGameState,
// client <--> server
KeepAlive,
Disconnect,
ChatMessage,
PlayerPositionAndRotation,
PlayerShoot,
PACKET_COUNT
};
class Packet {
public:
virtual PacketType GetType() const = 0;
virtual void Accept(PacketVisitor& a_Visitor) const = 0;
};
namespace packets {
/**
* \tparam PT The packet type
* \tparam Data The structure holding the data of the packet
*/
template <PacketType PT, typename Data>
class ConcretePacket : public Packet {
public:
using PacketDataType = Data;
PacketDataType m_Data;
ConcretePacket(const PacketDataType& a_Data = {});
constexpr PacketType GetType() const override {
return PT;
};
private:
void Accept(PacketVisitor& a_Visitor) const override;
friend class PacketVisitor;
};
// define BLITZ_INSTANCIATE_PACKETS
// before including this file
// if you want to instantiate templates
#ifdef BLITZ_INSTANCIATE_PACKETS
#define DeclarePacket(Type) \
using Type = ConcretePacket<PacketType::Type, data::Type>; \
template class ConcretePacket<PacketType::Type, data::Type>
#else
#define DeclarePacket(Type) using Type = ConcretePacket<PacketType::Type, data::Type>
#endif
DeclarePacket(PlayerLogin);
DeclarePacket(UpdateHealth);
DeclarePacket(LoggingSuccess);
DeclarePacket(PlayerDeath);
DeclarePacket(PlayerJoin);
DeclarePacket(PlayerLeave);
DeclarePacket(PlayerStats);
DeclarePacket(PlayerList);
DeclarePacket(ServerConfig);
DeclarePacket(ServerTps);
DeclarePacket(UpdateGameState);
DeclarePacket(KeepAlive);
DeclarePacket(Disconnect);
DeclarePacket(ChatMessage);
DeclarePacket(PlayerPositionAndRotation);
DeclarePacket(PlayerShoot);
} // namespace packets
} // namespace protocol
} // namespace blitz

View File

@@ -0,0 +1,70 @@
#pragma once
/**
* \file Test.h
* \brief File containing unit testing utilities
*/
#include <cstdlib>
#include <blitz/common/Log.h>
namespace blitz {
namespace test {
/**
* \def BLITZ_TEST_SUCCESSFUL
* \brief Used in tests to indicate that a test was successful
*/
#define BLITZ_TEST_SUCCESSFUL 0
/**
* \def BLITZ_TEST_FAILED
* \brief Used in tests to indicate that a test failed
*/
#define BLITZ_TEST_FAILED 1
#ifndef __FUNCTION_NAME__
#ifdef _WIN32
#define __FUNCTION_NAME__ __FUNCTION__
#else
#define __FUNCTION_NAME__ __PRETTY_FUNCTION__
#endif
#endif
/**
* \def blitz_test_assert
* \param ... The expression to evaluate
* \brief Evaluates the expression and exits the program if not valid.
* \note It works like a basic assert() but also in release mode
*/
#define blitz_test_assert(...) \
if (!static_cast<bool>(__VA_ARGS__)) { \
blitz::test::LogAssert(#__VA_ARGS__, __FILE__, __LINE__, __FUNCTION_NAME__); \
std::exit(BLITZ_TEST_FAILED); \
}
/**
* \def blitz_debug_assert
* \param ... The expression to execute
* \brief Assertion without checks in release mode
* \note The expression is always executed. However, in release, no checks are made !
*/
#ifdef NDEBUG
#define blitz_debug_assert(...) __VA_ARGS__
#else
#define blitz_debug_assert blitz_test_assert
#endif
/**
* \brief Prints an error message associated with a failed assertion
* \param expression The expression that was tested
* \param file The file in which the assertion failed
* \param line The line in the file in which the assertion failed
* \param function The function in which the assertion failed
*/
void LogAssert(const char* expression, const char* file, int line, const char* function);
} // namespace test
} // namespace blitz

35
src/blitz/common/Log.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include <blitz/common/Log.h>
#ifdef BLITZ_ANDROID_LOGGING
#include <android/log.h>
#else
#include <iostream>
#endif
namespace blitz {
void Log(const std::string& msg) {
#ifdef BLITZ_ANDROID_LOGGING
__android_log_print(ANDROID_LOG_INFO, "TRACKERS", "%s", msg.c_str());
#else
std::cout << msg << "\n";
#endif
}
void LogD(const std::string& msg) {
#if !defined(NDEBUG)
Log(msg);
#endif
}
void LogE(const std::string& err) {
#ifdef BLITZ_ANDROID_LOGGING
__android_log_print(ANDROID_LOG_ERROR, "TRACKERS", "%s", err.c_str());
#else
std::cerr << err << "\n";
#endif
}
} // namespace blitz

View File

@@ -0,0 +1,37 @@
#include <blitz/protocol/PacketFactory.h>
#include <array>
#include <functional>
namespace blitz {
namespace protocol {
namespace PacketFactory {
using PacketCreator = std::function<std::unique_ptr<Packet>()>;
static const std::array<std::unique_ptr<Packet>, static_cast<std::size_t>(PacketType::PACKET_COUNT)> Packets = {
std::make_unique<packets::PlayerLogin>(),
std::make_unique<packets::UpdateHealth>(),
std::make_unique<packets::LoggingSuccess>(),
std::make_unique<packets::PlayerDeath>(),
std::make_unique<packets::PlayerJoin>(),
std::make_unique<packets::PlayerLeave>(),
std::make_unique<packets::PlayerList>(),
std::make_unique<packets::PlayerStats>(),
std::make_unique<packets::ServerConfig>(),
std::make_unique<packets::ServerTps>(),
std::make_unique<packets::UpdateGameState>(),
std::make_unique<packets::KeepAlive>(),
std::make_unique<packets::Disconnect>(),
std::make_unique<packets::ChatMessage>(),
std::make_unique<packets::PlayerPositionAndRotation>(),
std::make_unique<packets::PlayerShoot>(),
};
const std::unique_ptr<Packet>& CreateReadOnlyPacket(PacketType a_Type) {
return Packets[static_cast<std::size_t>(a_Type)];
}
} // namespace PacketFactory
} // namespace protocol
} // namespace blitz

View File

@@ -0,0 +1,279 @@
#include <blitz/protocol/PacketSerializer.h>
#include <Nazara/Core/ByteStream.hpp>
#include <blitz/protocol/PacketFactory.h>
#include <blitz/protocol/PacketVisitor.h>
#include <iostream>
namespace blitz {
namespace protocol {
namespace PacketSerializer {
#define VisitSerialize(ClassName) \
void Visit(const ClassName& a_Packet) override { \
const auto& packetData = a_Packet.m_Data; \
SerializePacketData(packetData); \
} \
\
void SerializePacketData(const ClassName::PacketDataType& a_Packet)
#define VisitDeserialize(ClassName) \
void Visit(const ClassName& a_Packet) override { \
auto packetPtr = PacketFactory::CreatePacket<ClassName>(); \
auto& packetData = packetPtr->m_Data; \
\
DeserializePacketData(packetData); \
\
m_Packet = std::move(packetPtr); \
} \
\
void DeserializePacketData(ClassName::PacketDataType& a_Packet)
class Serializer : public PacketVisitor {
private:
Nz::ByteStream& m_Buffer;
public:
Serializer(Nz::ByteStream& a_Buffer) : m_Buffer(a_Buffer) {}
void Serialize(const Packet& a_Packet) {
m_Buffer << static_cast<PacketID>(a_Packet.GetType());
Check(a_Packet);
}
VisitSerialize(packets::PlayerLogin);
VisitSerialize(packets::UpdateHealth);
VisitSerialize(packets::LoggingSuccess);
VisitSerialize(packets::PlayerDeath);
VisitSerialize(packets::PlayerJoin);
VisitSerialize(packets::PlayerLeave);
VisitSerialize(packets::PlayerList);
VisitSerialize(packets::PlayerStats);
VisitSerialize(packets::ServerConfig);
VisitSerialize(packets::ServerTps);
VisitSerialize(packets::UpdateGameState);
VisitSerialize(packets::KeepAlive);
VisitSerialize(packets::Disconnect);
VisitSerialize(packets::ChatMessage);
VisitSerialize(packets::PlayerPositionAndRotation);
VisitSerialize(packets::PlayerShoot);
};
class Deserializer : public PacketVisitor {
private:
Nz::ByteStream& m_Buffer;
PacketPtr m_Packet;
public:
Deserializer(Nz::ByteStream&& a_Buffer) : m_Buffer(a_Buffer) {}
bool Deserialize(const PacketPtr& a_Packet) {
try {
Check(*a_Packet.get());
} catch (std::exception& e) {
return false;
}
return true;
}
PacketPtr& GetPacket() {
return m_Packet;
}
VisitDeserialize(packets::PlayerLogin);
VisitDeserialize(packets::UpdateHealth);
VisitDeserialize(packets::LoggingSuccess);
VisitDeserialize(packets::PlayerDeath);
VisitDeserialize(packets::PlayerJoin);
VisitDeserialize(packets::PlayerLeave);
VisitDeserialize(packets::PlayerList);
VisitDeserialize(packets::PlayerStats);
VisitDeserialize(packets::ServerConfig);
VisitDeserialize(packets::ServerTps);
VisitDeserialize(packets::UpdateGameState);
VisitDeserialize(packets::KeepAlive);
VisitDeserialize(packets::Disconnect);
VisitDeserialize(packets::ChatMessage);
VisitDeserialize(packets::PlayerPositionAndRotation);
VisitDeserialize(packets::PlayerShoot);
};
Nz::ByteArray Serialize(const Packet& a_Packet) {
Nz::ByteArray buffer;
Nz::ByteStream stream(&buffer, Nz::OpenMode::Write);
Serializer serializer(stream);
serializer.Serialize(a_Packet);
buffer.ShrinkToFit();
return buffer;
}
std::unique_ptr<Packet> Deserialize(Nz::ByteArray& a_Data) {
Nz::ByteStream stream(&a_Data, Nz::OpenMode::Read);
PacketID packetId;
stream >> packetId;
if (packetId >= static_cast<PacketID>(PacketType::PACKET_COUNT))
return nullptr;
PacketType packetType = PacketType(packetId);
// for double-dispatch
const PacketPtr& emptyPacket = PacketFactory::CreateReadOnlyPacket(packetType);
Deserializer deserializer(std::move(stream));
if (deserializer.Deserialize(emptyPacket)) {
PacketPtr packet = std::move(deserializer.GetPacket());
return packet;
}
return nullptr;
}
//---------------------------------------------
// Packet serializer implementation
//----------------------------------------------
void Serializer::SerializePacketData(const data::PlayerLogin& a_Packet) {
m_Buffer << a_Packet.m_PlayerName;
}
void Deserializer::DeserializePacketData(data::PlayerLogin& a_Packet) {
m_Buffer >> a_Packet.m_PlayerName;
}
void Serializer::SerializePacketData(const data::UpdateHealth& a_Packet) {
m_Buffer << a_Packet.m_NewHealth;
}
void Deserializer::DeserializePacketData(data::UpdateHealth& a_Packet) {
m_Buffer >> a_Packet.m_NewHealth;
}
void Serializer::SerializePacketData(const data::LoggingSuccess& a_Packet) {}
void Deserializer::DeserializePacketData(data::LoggingSuccess& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerDeath& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerDeath& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerJoin& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerJoin& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerLeave& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerLeave& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerList& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerList& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerStats& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerStats& a_Packet) {}
void Serializer::SerializePacketData(const data::ServerConfig& a_Packet) {}
void Deserializer::DeserializePacketData(data::ServerConfig& a_Packet) {}
void Serializer::SerializePacketData(const data::ServerTps& a_Packet) {}
void Deserializer::DeserializePacketData(data::ServerTps& a_Packet) {}
void Serializer::SerializePacketData(const data::UpdateGameState& a_Packet) {}
void Deserializer::DeserializePacketData(data::UpdateGameState& a_Packet) {}
void Serializer::SerializePacketData(const data::KeepAlive& a_Packet) {}
void Deserializer::DeserializePacketData(data::KeepAlive& a_Packet) {}
void Serializer::SerializePacketData(const data::Disconnect& a_Packet) {}
void Deserializer::DeserializePacketData(data::Disconnect& a_Packet) {}
void Serializer::SerializePacketData(const data::ChatMessage& a_Packet) {}
void Deserializer::DeserializePacketData(data::ChatMessage& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerPositionAndRotation& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerPositionAndRotation& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerShoot& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerShoot& a_Packet) {}
} // namespace PacketSerializer
} // namespace protocol
} // namespace blitz

View File

@@ -0,0 +1,11 @@
#include <blitz/protocol/PacketVisitor.h>
namespace blitz {
namespace protocol {
void PacketVisitor::Check(const Packet& a_Packet) {
a_Packet.Accept(*this);
}
} // namespace protocol
} // namespace blitz

View File

@@ -0,0 +1,18 @@
#define BLITZ_INSTANCIATE_PACKETS
#include <blitz/protocol/Packets.h>
#include <blitz/protocol/PacketVisitor.h>
namespace blitz {
namespace protocol {
template <PacketType PT, typename Data>
packets::ConcretePacket<PT, Data>::ConcretePacket(const PacketDataType& a_Data) : m_Data(a_Data) {}
template <PacketType PT, typename Data>
void packets::ConcretePacket<PT, Data>::Accept(PacketVisitor& a_Visitor) const {
a_Visitor.Visit(*this);
}
} // namespace protocol
} // namespace blitz

14
src/blitz/utils/Test.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include <blitz/utils/Test.h>
#include <blitz/common/Format.h>
#include <blitz/common/Log.h>
namespace blitz {
namespace test {
void LogAssert(const char* expression, const char* file, int line, const char* function) {
LogE(Format("%s:%i: %s: Assertion failed !", file, line, function));
LogE(Format(" %i |\t%s;", line, expression));
}
} // namespace utils
} // namespace blitz

View File

@@ -0,0 +1,15 @@
#include <blitz/protocol/PacketFactory.h>
static int Test() {
for (std::size_t i = 0; i < static_cast<int>(blitz::protocol::PacketType::PACKET_COUNT); i++) {
blitz::protocol::PacketType packetType = blitz::protocol::PacketType(i);
if (blitz::protocol::PacketFactory::CreateReadOnlyPacket(packetType)->GetType() != packetType)
return 1;
}
return 0;
}
int main() {
return Test();
}

View File

@@ -0,0 +1,23 @@
#include <blitz/protocol/PacketSerializer.h>
#include <blitz/protocol/PacketFactory.h>
#include <blitz/utils/Test.h>
static int Test() {
for (std::size_t i = 0; i < static_cast<std::size_t>(blitz::protocol::PacketType::PACKET_COUNT); i++) {
const auto& packet = blitz::protocol::PacketFactory::CreateReadOnlyPacket(blitz::protocol::PacketType(i));
Nz::ByteArray buffer = blitz::protocol::PacketSerializer::Serialize(*packet.get());
blitz::protocol::PacketPtr packet2 = blitz::protocol::PacketSerializer::Deserialize(buffer);
blitz_test_assert(packet2 != nullptr);
blitz_test_assert(packet2->GetType() == packet->GetType());
}
return 0;
}
int main() {
return Test();
}

View File

@@ -4,13 +4,44 @@ add_repositories("nazara-repo https://github.com/NazaraEngine/xmake-repo.git")
add_requires("nazaraengine", { debug = is_mode("debug") }) add_requires("nazaraengine", { debug = is_mode("debug") })
set_languages("c++20") set_languages("c++20")
set_warnings("all")
target("BlitzLib")
set_kind("static")
add_files("src/blitz/**.cpp")
add_includedirs("include")
add_packages("nazaraengine")
target("Blitz2") target("Blitz2")
set_kind("binary") set_kind("binary")
add_files("src/*.cpp") add_files("src/*.cpp")
add_packages("nazaraengine") add_packages("nazaraengine")
add_includedirs("include")
add_deps("BlitzLib")
set_rundir(".") set_rundir(".")
-- Tests were introduced in that version
set_xmakever("2.8.5")
-- Tests
for _, file in ipairs(os.files("test/**.cpp")) do
local name = path.basename(file)
target(name)
set_kind("binary")
add_includedirs("include", "test")
add_files(file)
set_default(false)
add_deps("BlitzLib")
add_packages("nazaraengine")
add_tests("compile_and_run")
end
-- --
-- If you want to known more usage about xmake, please see https://xmake.io -- If you want to known more usage about xmake, please see https://xmake.io
-- --