feat: add streams
All checks were successful
Linux arm64 / Build (push) Successful in 16s

This commit is contained in:
2025-06-26 19:17:52 +02:00
parent 59bedd6482
commit 0d26879152
25 changed files with 385 additions and 778 deletions

View File

@@ -1,60 +0,0 @@
#pragma once
#include <cxxabi.h>
#include <string>
namespace sp {
template <typename T>
std::string GetBasicClassName(const T& a_Value) {
int status;
char* demangled = abi::__cxa_demangle(typeid(a_Value).name(), 0, 0, &status);
if (status != 0)
return "";
return std::string(demangled);
}
template <typename T>
std::string GetBasicClassName() {
int status;
char* demangled = abi::__cxa_demangle(typeid(T).name(), 0, 0, &status);
if (status != 0)
return "";
return std::string(demangled);
}
template <typename T>
struct Reflector {
static std::string GetClassName() {
return GetBasicClassName<T>();
}
};
template <>
struct Reflector<std::string> {
static std::string GetClassName() {
return "std::string";
}
};
template <typename K, typename V>
struct Reflector<std::map<K, V>> {
static std::string GetClassName();
};
template <typename T>
struct Reflector<std::vector<T>> {
static std::string GetClassName();
};
template <typename T>
std::string Reflector<std::vector<T>>::GetClassName() {
return "std::vector<" + Reflector<T>::GetClassName() + ">";
}
template <typename K, typename V>
std::string Reflector<std::map<K, V>>::GetClassName() {
return "std::map<" + Reflector<K>::GetClassName() + ", " + Reflector<V>::GetClassName() + ">";
}
} // namespace sp

View File

@@ -7,12 +7,14 @@
#include <cstddef>
#include <cstdint>
#include <ostream>
#include <functional>
namespace sp {
class DataBuffer;
using ReadFunc = std::function<void(std::uint8_t&)>;
/**
* \class VarInt
* \brief Variable-length format such that smaller numbers use fewer bytes.
@@ -57,6 +59,7 @@ class VarInt {
*/
friend DataBuffer& operator>>(DataBuffer& in, VarInt& var);
void Read(const ReadFunc& read);
};
} // namespace sp

View File

@@ -1,33 +0,0 @@
#pragma once
#include <fstream>
#include <sp/io/IOInterface.h>
namespace sp {
namespace io {
struct FileTag {
enum OpenMode {
In = 1,
Out = 1 << 1,
};
};
template <>
class IOInterface<FileTag> {
private:
std::unique_ptr<std::ifstream> m_FileInput;
std::unique_ptr<std::ofstream> m_FileOutput;
public:
IOInterface(const std::string& a_FilePath, unsigned int a_OpenMode);
IOInterface(IOInterface&& other);
DataBuffer Read(std::size_t a_Amount);
void Write(const sp::DataBuffer& a_Data);
};
using File = IOInterface<FileTag>;
} // namespace io
} // namespace sp

View File

@@ -1,58 +0,0 @@
#pragma once
#include <memory>
#include <sp/common/DataBuffer.h>
namespace sp {
namespace io {
template <typename IOTag>
class IOInterface {
public:
DataBuffer Read(std::size_t a_Amount);
void Write(const DataBuffer& a_Data);
};
template <typename TOption>
class MessageEncapsulator {
public:
static DataBuffer Encapsulate(const DataBuffer& a_Data, const TOption& a_Option);
static DataBuffer Decapsulate(DataBuffer& a_Data, const TOption& a_Option);
};
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
class Stream {
protected:
MessageDispatcher m_Dispatcher;
IOInterface<IOTag> m_Interface;
std::tuple<TOptions...> m_Options;
using MessageBase = typename MessageDispatcher::MessageBaseType;
public:
Stream() {}
Stream(IOInterface<IOTag>&& a_Interface, TOptions&&... a_Options);
Stream(Stream&& a_Stream);
void RecieveMessages();
void SendMessage(const MessageBase& a_Message);
template <typename TOption>
TOption& GetOption() {
return std::get<TOption>(m_Options);
}
MessageDispatcher& GetDispatcher() {
return m_Dispatcher;
}
private:
static DataBuffer Encapsulate(const DataBuffer& a_Data, const TOptions&... a_Options);
static DataBuffer Decapsulate(DataBuffer& a_Data, const TOptions&... a_Options);
};
} // namespace io
} // namespace sp
#include <sp/io/IOInterfaceImpl.inl>

View File

@@ -1,129 +0,0 @@
#pragma once
#include <sp/extensions/Compress.h>
#include <stdexcept>
namespace sp {
namespace details {
template <typename... TOptions>
struct MessageEncapsulatorPack {};
template <>
struct MessageEncapsulatorPack<> {
static DataBuffer Encapsulate(const DataBuffer& a_Data) {
return a_Data;
}
static DataBuffer Decapsulate(DataBuffer& a_Data) {
return a_Data;
}
};
template <typename TOption, typename... TOptions>
struct MessageEncapsulatorPack<TOption, TOptions...> {
static DataBuffer Encapsulate(const DataBuffer& a_Data, const TOption& a_Option, const TOptions&... a_Options) {
DataBuffer data = io::MessageEncapsulator<TOption>::Encapsulate(a_Data, a_Option);
return MessageEncapsulatorPack<TOptions...>::Encapsulate(data, a_Options...);
}
static DataBuffer Decapsulate(DataBuffer& a_Data, const TOption& a_Option, const TOptions&... a_Options) {
DataBuffer data = io::MessageEncapsulator<TOption>::Decapsulate(a_Data, a_Option);
return MessageEncapsulatorPack<TOptions...>::Decapsulate(data, a_Options...);
}
};
} // namespace details
namespace io {
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::Stream(IOInterface<IOTag>&& a_Interface, TOptions&&... a_Options) :
m_Interface(std::move(a_Interface)), m_Options(std::make_tuple<TOptions...>(std::move(a_Options)...)) {}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::Stream(
Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>&& a_Stream) :
m_Dispatcher(std::move(a_Stream.m_Dispatcher)), m_Interface(std::move(a_Stream.m_Interface)) {}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
void Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::SendMessage(const MessageBase& a_Message) {
DataBuffer data = a_Message.Write();
TupleForEach([&data](const auto&... a_Options) {
data = Encapsulate(data, a_Options...);
}, m_Options);
DataBuffer finalData;
finalData.Reserve(data.GetSize() + sizeof(VarInt::MAX_VALUE));
finalData << VarInt{data.GetSize()} << data;
m_Interface.Write(finalData);
}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
void Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::RecieveMessages() {
while (true) {
// reading the first VarInt part byte by byte
std::uint64_t lenghtValue = 0;
unsigned int readPos = 0;
while (true) {
static constexpr int SEGMENT_BITS = (1 << 7) - 1;
static constexpr int CONTINUE_BIT = 1 << 7;
DataBuffer buffer = m_Interface.Read(sizeof(std::uint8_t));
// eof
if (buffer.GetSize() == 0)
return;
std::uint8_t part;
buffer >> part;
lenghtValue |= static_cast<std::uint64_t>(part & SEGMENT_BITS) << readPos;
if ((part & CONTINUE_BIT) == 0)
break;
readPos += 7;
if (readPos >= 8 * sizeof(lenghtValue))
throw std::runtime_error("VarInt is too big");
}
// nothing to read
if (lenghtValue == 0)
return;
DataBuffer buffer;
buffer = m_Interface.Read(lenghtValue);
TupleForEach([&buffer, lenghtValue](const auto&... a_Options) {
buffer = Decapsulate(buffer, a_Options...);
}, m_Options);
VarInt packetType;
buffer >> packetType;
static const MessageFactory messageFactory;
std::unique_ptr<MessageBase> message = messageFactory.CreateMessage(packetType.GetValue());
assert(message != nullptr);
message->Read(buffer);
GetDispatcher().Dispatch(*message);
}
}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
DataBuffer Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::Encapsulate(
const DataBuffer& a_Data, const TOptions&... a_Options) {
return details::MessageEncapsulatorPack<TOptions...>::Encapsulate(a_Data, a_Options...);
}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
DataBuffer Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::Decapsulate(
DataBuffer& a_Data, const TOptions&... a_Options) {
return details::MessageEncapsulatorPack<TOptions...>::Decapsulate(a_Data, a_Options...);
}
} // namespace io
} // namespace sp

View File

@@ -0,0 +1,13 @@
#pragma once
#include <sp/common/DataBuffer.h>
namespace sp {
class IoInterface {
public:
virtual DataBuffer Read(std::size_t a_Amount) = 0;
virtual void Write(const DataBuffer& a_Data) = 0;
};
} // namespace sp

View File

@@ -1,23 +0,0 @@
#pragma once
#include <sp/io/IOInterface.h>
namespace sp {
namespace io {
struct MemoryTag {};
template <>
class IOInterface<MemoryTag> {
private:
sp::DataBuffer m_VirtualIO;
public:
sp::DataBuffer Read(std::size_t a_Amount);
void Write(const sp::DataBuffer& a_Data);
};
using Memory = IOInterface<MemoryTag>;
} // namespace io
} // namespace sp

View File

@@ -0,0 +1,16 @@
#pragma once
#include <sp/common/DataBuffer.h>
namespace sp {
class MessageEncapsulator {
public:
MessageEncapsulator() {}
virtual ~MessageEncapsulator() {}
virtual DataBuffer Encapsulate(const DataBuffer& a_Data) = 0;
virtual DataBuffer Decapsulate(DataBuffer& a_Data) = 0;
};
} // namespace sp

36
include/sp/io/MessageIO.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <boost/pfr.hpp>
#include <sp/common/ByteSwapping.h>
#include <sp/common/DataBuffer.h>
namespace sp {
namespace details {
template <typename T>
void WriteField(DataBuffer& a_Buffer, const T& a_Data) {
T swapped = a_Data;
ToNetwork(swapped);
a_Buffer << swapped;
}
template <typename T>
void ReadField(DataBuffer& a_Buffer, T& a_Data) {
a_Buffer >> a_Data;
FromNetwork(a_Data);
}
template <typename TData>
DataBuffer WriteMessage(const TData& a_MessageData) {
DataBuffer buffer;
boost::pfr::for_each_field(a_MessageData, [&buffer](const auto& a_Field) { WriteField(buffer, a_Field); });
return buffer;
}
template <typename TData>
void ReadMessage(DataBuffer& a_Buffer, TData& a_MessageData) {
boost::pfr::for_each_field(a_MessageData, [&a_Buffer](auto& a_Field) { ReadField(a_Buffer, a_Field); });
}
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,34 @@
#pragma once
#include <sp/common/DataBuffer.h>
#include <sp/io/IoInterface.h>
#include <sp/io/MessageEncapsulator.h>
#include <vector>
namespace sp {
template <typename TMessageFactory>
class MessageStream {
private:
std::vector<MessageEncapsulator> m_Encapsulators;
std::shared_ptr<IoInterface> m_Stream;
using MessageBaseType = typename TMessageFactory::MessageBaseType;
using MessageIdType = typename MessageBaseType::MessageIdType;
public:
MessageStream(std::shared_ptr<IoInterface>&& a_Stream) : m_Stream(std::move(a_Stream)) {}
std::unique_ptr<MessageBaseType> ReadMessage();
std::unique_ptr<MessageBaseType> ReadMessage(MessageIdType a_Id);
void WriteMessage(const MessageBaseType& a_Message, bool a_WriteId = true);
private:
DataBuffer ReadAndDecapsulate();
std::unique_ptr<MessageBaseType> MakeMessage(DataBuffer& buffer, MessageIdType a_Id);
};
} // namespace sp
#include <sp/io/MessageStream.inl>

View File

@@ -0,0 +1,63 @@
#pragma once
#include <sp/common/VarInt.h>
#include <sp/protocol/MessageFactory.h>
namespace sp {
template <typename TMessageFactory>
DataBuffer MessageStream<TMessageFactory>::ReadAndDecapsulate() {
VarInt messageLength;
messageLength.Read([this](std::uint8_t& data) { m_Stream->Read(1) >> data; });
std::size_t amount = messageLength.GetValue();
DataBuffer buffer = m_Stream->Read(amount);
for (MessageEncapsulator& enc : m_Encapsulators) {
buffer = enc.Decapsulate(buffer);
}
return buffer;
}
template <typename TMessageFactory>
std::unique_ptr<typename TMessageFactory::MessageBaseType> MessageStream<TMessageFactory>::MakeMessage(DataBuffer& buffer, MessageIdType a_Id) {
static const TMessageFactory FACTORY;
auto message = FACTORY.CreateMessage(a_Id);
message->Read(buffer);
return message;
}
template <typename TMessageFactory>
std::unique_ptr<typename TMessageFactory::MessageBaseType> MessageStream<TMessageFactory>::ReadMessage(MessageIdType a_Id) {
DataBuffer buffer = ReadAndDecapsulate();
return MakeMessage(buffer, a_Id);
}
template <typename TMessageFactory>
std::unique_ptr<typename TMessageFactory::MessageBaseType> MessageStream<TMessageFactory>::ReadMessage() {
DataBuffer buffer = ReadAndDecapsulate();
VarInt messageId;
buffer >> messageId;
return MakeMessage(buffer, MessageIdType(messageId.GetValue()));
}
template <typename TMessageFactory>
void MessageStream<TMessageFactory>::WriteMessage(const MessageBaseType& a_Message, bool a_WriteId) {
DataBuffer buffer;
if (a_WriteId)
buffer << VarInt{static_cast<std::uint64_t>(a_Message.GetId())};
buffer << a_Message.Write();
for (MessageEncapsulator& enc : m_Encapsulators) {
buffer = enc.Encapsulate(buffer);
}
DataBuffer header;
header << VarInt{buffer.GetSize()};
m_Stream->Write(header);
m_Stream->Write(buffer);
}
} // namespace sp

40
include/sp/io/StdIo.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <sp/io/IoInterface.h>
#include <iostream>
namespace sp {
class StdInput : public IoInterface {
private:
std::istream& m_Io;
public:
StdInput(std::istream& a_Io) : m_Io(a_Io) {}
virtual DataBuffer Read(std::size_t a_Amount) override {
DataBuffer buffer(a_Amount);
m_Io.read(reinterpret_cast<char*>(buffer.data()), a_Amount);
return buffer;
}
virtual void Write(const DataBuffer& a_Data) override {}
};
class StdOuput : public IoInterface {
private:
std::ostream& m_Io;
public:
StdOuput(std::ostream& a_Io) : m_Io(a_Io) {}
virtual DataBuffer Read(std::size_t a_Amount) override {
return {};
}
virtual void Write(const DataBuffer& a_Data) override {
m_Io.write(reinterpret_cast<const char*>(a_Data.data()), a_Data.GetSize());
}
};
} // namespace sp

View File

@@ -1,7 +1,7 @@
#pragma once
#include <sp/protocol/MessageBase.h>
#include <sp/protocol/MessageIO.h>
#include <sp/io/MessageIO.h>
namespace sp {
@@ -9,7 +9,6 @@ template <typename TData, typename TMessageID, TMessageID ID, typename THandler>
class ConcreteMessage : public MessageBase<TMessageID, THandler> {
public:
using DataType = TData;
using ThisType = ConcreteMessage<TData, TMessageID, ID, THandler>;
template <typename... T>
ConcreteMessage(const T&... args) : m_Data{args...} {}
@@ -21,15 +20,15 @@ class ConcreteMessage : public MessageBase<TMessageID, THandler> {
}
virtual void Dispatch(THandler& handler) const override {
handler.Handle(static_cast<const ThisType&>(*this));
handler.Handle(static_cast<const DataType&>(m_Data));
}
virtual void Read(std::istream& a_Is) override {
details::ReadMessage(a_Is, m_Data);
virtual void Read(DataBuffer& a_Buffer) override {
details::ReadMessage(a_Buffer, m_Data);
}
virtual void Write(std::ostream& a_Os) const override {
details::WriteMessage(a_Os, m_Data);
virtual DataBuffer Write() const override {
return details::WriteMessage(m_Data);
}
private:

View File

@@ -1,6 +1,7 @@
#pragma once
#include <iosfwd>
#include <sp/common/DataBuffer.h>
namespace sp {
@@ -17,8 +18,8 @@ class MessageBase {
virtual void Dispatch(HandlerType& handler) const = 0;
virtual void Read(std::istream& a_Is) = 0;
virtual void Write(std::ostream& a_Os) const = 0;
virtual void Read(DataBuffer& a_Buffer) = 0;
virtual DataBuffer Write() const = 0;
};
} // namespace sp

View File

@@ -12,6 +12,7 @@ template <typename TBase, typename TTMessages>
class MessageFactory {
public:
using IdType = typename TBase::MessageIdType;
using MessageBaseType = TBase;
MessageFactory() {
constexpr std::size_t messageCount = std::tuple_size_v<TTMessages>;

View File

@@ -0,0 +1,133 @@
#pragma once
#include <tuple>
namespace sp
{
// This class is inspired by https://arobenko.gitbooks.io/comms-protocols-cpp/content/
// TAll is all the message types, that need to be handled, bundled in std::tuple
template <typename TAll>
class MessageHandler;
// Big boy to process packets 20 by 20, preventing needlessly copying vtable many times at each inheritance stage
template <typename T1, typename T2, typename T3, typename T4, typename T5,
typename T6, typename T7, typename T8, typename T9, typename T10,
typename T11, typename T12, typename T13, typename T14, typename T15,
typename T16, typename T17, typename T18, typename T19, typename T20,
typename... TRest>
class MessageHandler<std::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T11, T13, T14, T15, T16, T17, T18, T19, T20, TRest...> > : public MessageHandler<std::tuple<TRest...> >
{
using Base = MessageHandler<std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
virtual void Handle(const typename T4::DataType& msg) {}
virtual void Handle(const typename T5::DataType& msg) {}
virtual void Handle(const typename T6::DataType& msg) {}
virtual void Handle(const typename T7::DataType& msg) {}
virtual void Handle(const typename T8::DataType& msg) {}
virtual void Handle(const typename T9::DataType& msg) {}
virtual void Handle(const typename T10::DataType& msg) {}
virtual void Handle(const typename T11::DataType& msg) {}
virtual void Handle(const typename T12::DataType& msg) {}
virtual void Handle(const typename T13::DataType& msg) {}
virtual void Handle(const typename T14::DataType& msg) {}
virtual void Handle(const typename T15::DataType& msg) {}
virtual void Handle(const typename T16::DataType& msg) {}
virtual void Handle(const typename T17::DataType& msg) {}
virtual void Handle(const typename T18::DataType& msg) {}
virtual void Handle(const typename T19::DataType& msg) {}
virtual void Handle(const typename T20::DataType& msg) {}
};
// 10 by 10
template <typename T1, typename T2, typename T3, typename T4, typename T5,
typename T6, typename T7, typename T8, typename T9, typename T10,
typename... TRest>
class MessageHandler<std::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TRest...> > : public MessageHandler<std::tuple<TRest...> >
{
using Base = MessageHandler<std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
virtual void Handle(const typename T4::DataType& msg) {}
virtual void Handle(const typename T5::DataType& msg) {}
virtual void Handle(const typename T6::DataType& msg) {}
virtual void Handle(const typename T7::DataType& msg) {}
virtual void Handle(const typename T8::DataType& msg) {}
virtual void Handle(const typename T9::DataType& msg) {}
virtual void Handle(const typename T10::DataType& msg) {}
};
// 5 by 5
template <
typename T1, typename T2, typename T3, typename T4, typename T5,
typename... TRest>
class MessageHandler<std::tuple<T1, T2, T3, T4, T5, TRest...> > : public MessageHandler<std::tuple<TRest...> >
{
using Base = MessageHandler<std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
virtual void Handle(const typename T4::DataType& msg) {}
virtual void Handle(const typename T5::DataType& msg) {}
};
// Deal with rest with 4 types
template <typename T1, typename T2, typename T3, typename T4>
class MessageHandler<std::tuple<T1, T2, T3, T4> >
{
public:
virtual ~MessageHandler() {}
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
virtual void Handle(const typename T4::DataType& msg) {}
};
// Deal with rest with 3 types
template < typename T1, typename T2, typename T3>
class MessageHandler<std::tuple<T1, T2, T3> >
{
public:
virtual ~MessageHandler() {}
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
};
// Deal with rest with 2 types
template <typename T1, typename T2>
class MessageHandler<std::tuple<T1, T2> >
{
public:
virtual ~MessageHandler() {}
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
};
// Deal with rest with 1 type
template <typename T1>
class MessageHandler<std::tuple<T1> >
{
public:
virtual ~MessageHandler() {}
virtual void Handle(const typename T1::DataType& msg) {}
};
// Deal with rest with 0 type
template <>
class MessageHandler<std::tuple<> >
{
public:
virtual ~MessageHandler() {}
};
} // sp

View File

@@ -1,38 +0,0 @@
#pragma once
#include <boost/pfr.hpp>
#include <iostream>
#include <sp/common/ByteSwapping.h>
namespace sp {
namespace details {
template<typename T>
void WriteField(std::ostream& a_Os, const T& a_Data) {
T swapped = a_Data;
ToNetwork(swapped);
a_Os.write(reinterpret_cast<const char*>(&swapped), sizeof(a_Data));
}
template<typename T>
void ReadField(std::istream& a_Is, T& a_Data) {
a_Is.read(reinterpret_cast<char*>(&a_Data), sizeof(a_Data));
FromNetwork(a_Data);
}
template <typename TData>
void WriteMessage(std::ostream& a_Os, const TData& a_MessageData) {
boost::pfr::for_each_field(a_MessageData, [&a_Os](const auto& a_Field) {
WriteField(a_Os, a_Field);
});
}
template <typename TData>
void ReadMessage(std::istream& a_Is, TData& a_MessageData) {
boost::pfr::for_each_field(a_MessageData, [&a_Is](auto& a_Field) {
ReadField(a_Is, a_Field);
});
}
} // namespace details
} // namespace sp