23 Commits

Author SHA1 Message Date
f145716cf6 serialize maps 2025-02-23 11:27:57 +01:00
6ee7524e17 update README 2025-02-23 10:50:39 +01:00
db0c5f3245 fix alignment + refactor
Reviewed-on: #1
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-02-23 09:40:46 +00:00
ee865021c2 update varint constants 2025-02-08 20:08:19 +01:00
462086b13c feat: add alignment 2025-02-08 19:45:45 +01:00
530232ba16 add back tupleforeach 2025-02-08 19:39:37 +01:00
dd808b4d57 remove unused functions 2025-02-08 14:52:28 +01:00
f5a3a443af optimise endian 2025-02-08 14:38:18 +01:00
06d69fb976 feat: add support for all endianesses 2025-02-08 13:44:04 +01:00
5e89531508 cpp version back to c++17 2025-02-07 22:03:14 +01:00
51d7c8f66d update example 2025-02-07 22:01:09 +01:00
534757f884 better endianess handling 2025-02-07 22:01:04 +01:00
6725a63c07 add write id 2025-02-07 21:10:06 +01:00
032800b220 credits 2025-02-06 22:35:00 +01:00
e13c73fe59 clean MessageFactory 2025-02-06 22:32:59 +01:00
097dab01fd clean up namespaces 2025-02-06 22:23:40 +01:00
bbafae2588 working message factory 2025-02-06 22:16:07 +01:00
f5430f3d0f add todos 2025-02-06 19:22:35 +01:00
7108ed4f28 constructor for packets 2025-02-06 19:01:32 +01:00
a63395f219 better fields 2025-02-06 18:30:35 +01:00
8ecfb95167 fields builder 2025-02-06 18:01:27 +01:00
c3a54d8e5b cleaner example 2025-02-06 16:14:11 +01:00
e6a58eb999 better example 2025-02-06 16:04:51 +01:00
45 changed files with 1414 additions and 1289 deletions

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# SimpleProtocolLib
Network engine used to (mainly) communicate with packets
# Integrate with xmake
```lua
add_repositories("persson-repo https://git.ale-pri.com/Persson-dev/xmake-repo.git")
add_requires("splib", { debug = is_mode("debug") })
```

View File

@@ -0,0 +1,20 @@
#pragma once
#include <sp/default/DefaultPacket.h>
#include <sp/protocol/Field.h>
#include <sp/protocol/MessageBase.h>
enum class DisconnectFieldsE {
Reason = 0
};
using DisconnectFields = std::tuple<std::string /*Reason*/>;
DeclarePacket(Disconnect){
public:
PacketConstructor(Disconnect)
const std::string& GetReason() const {
return GetField<DisconnectFieldsE, DisconnectFieldsE::Reason>();
}
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include <sp/default/DefaultPacket.h>
#include <sp/protocol/Field.h>
#include <sp/protocol/MessageBase.h>
enum class KeepAliveFieldsE {
KeepAliveId = 0,
};
using KeepAliveFields = std::tuple<
std::uint64_t //<- KeepAliveId
>;
DeclarePacket(KeepAlive){
public:
PacketConstructor(KeepAlive)
std::uint64_t GetKeepAliveId() const {
return GetField<KeepAliveFieldsE, KeepAliveFieldsE::KeepAliveId>();
}
};

View File

@@ -0,0 +1,17 @@
#pragma once
enum PacketId {
KeepAlive = 0,
Disconnect,
UpgradeTower,
};
#include <examples/KeepAlivePacket.h>
#include <examples/DisconnectPacket.h>
#include <examples/UpgradeTowerPacket.h>
// they must be in the same order as in the enum !
using AllPackets = std::tuple<KeepAlivePacket, DisconnectPacket, UpgradeTowerPacket>;
#include <sp/default/DefaultPacketHandler.h>
#include <sp/default/DefaultPacketFactory.h>

View File

@@ -0,0 +1,31 @@
#pragma once
#include <sp/default/DefaultPacket.h>
#include <sp/protocol/Field.h>
#include <sp/protocol/MessageBase.h>
enum class UpgradeTowerFieldsE {
m_Tower = 0,
m_Upgrade,
};
using UpgradeTowerFields = std::tuple<
sp::BitField<std::uint16_t,
sp::Field<std::uint16_t, 12>, //<- m_Tower
sp::Field<std::uint8_t, 4> //<- m_Upgrade
>
>;
DeclarePacket(UpgradeTower){
public:
PacketConstructor(UpgradeTower)
std::uint16_t GetTowerId() const {
return GetField<0>().GetField<UpgradeTowerFieldsE, UpgradeTowerFieldsE::m_Tower>();
}
std::uint8_t GetTowerUpgrade() const {
return GetField<0>().GetField<UpgradeTowerFieldsE, UpgradeTowerFieldsE::m_Upgrade>();
}
};

View File

@@ -1,65 +0,0 @@
#pragma once
#include <sp/common/DataBuffer.h>
namespace sp {
template <typename ValueType>
class Field {
public:
// Provide an access to the stored value
ValueType& GetValue() {
return m_Value;
}
const ValueType& GetValue() const {
return m_Value;
}
// Read (deserialise) and update internal value
void Read(DataBuffer& buffer) {
buffer >> m_Value;
}
// Write (serialise) internal value
void Write(DataBuffer& buffer) const {
buffer << m_Value;
}
Field& operator=(const ValueType& value) {
m_Value = value;
return *this;
}
private:
ValueType m_Value;
};
// Functor used to read all tuple values
class FieldReader {
public:
FieldReader(DataBuffer& buffer) : m_Buffer(buffer) {}
template <typename TField>
void operator()(TField& field) {
field.Read(m_Buffer);
}
private:
DataBuffer& m_Buffer;
};
// Functor used to write all tuple values
class FieldWriter {
public:
FieldWriter(DataBuffer& buffer) : m_Buffer(buffer) {}
template <typename TField>
void operator()(TField& field) {
field.Write(m_Buffer);
}
private:
DataBuffer& m_Buffer;
};
} // namespace sp

View File

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

View File

@@ -1,13 +0,0 @@
#pragma once
#include <sp/MessageInterfaceBuilder.h>
namespace sp {
template <typename... TOptions>
class Message : public option::MessageInterfaceBuilder<TOptions...>::Type {
public:
using ParsedOptions = typename option::MessageInterfaceBuilder<TOptions...>::ParsedOptions;
};
} // namespace sp

View File

@@ -1,278 +0,0 @@
#pragma once
#include <sp/Message.h>
#include <sp/Templates.h>
namespace sp {
namespace option {
// Provide static numeric ID, to facilitate implementation of GetIdImpl()
template <std::intmax_t TId>
struct StaticNumIdImpl {};
// Facilitate implementation of DispatchImpl()
template <typename TActual>
struct DispatchImpl {};
// Provide fields of the message, facilitate implementation of
// ReadImpl(), WriteImpl(), ValidImpl(), etc...
template <typename TFields>
struct FieldsImpl {};
} // namespace option
template <typename... TOptions>
class MessageImplParsedOptions;
template <>
struct MessageImplParsedOptions<> {
static const bool HasStaticNumIdImpl = false;
static const bool HasDispatchImpl = false;
static const bool HasFieldsImpl = false;
};
template <std::intmax_t TId, typename... TOptions>
struct MessageImplParsedOptions<option::StaticNumIdImpl<TId>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasStaticNumIdImpl = true;
static const std::intmax_t MsgId = TId;
};
template <typename TActual, typename... TOptions>
struct MessageImplParsedOptions<option::DispatchImpl<TActual>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasDispatchImpl = true;
using ActualMessage = TActual;
};
template <typename TFields, typename... TOptions>
struct MessageImplParsedOptions<option::FieldsImpl<TFields>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasFieldsImpl = true;
using Fields = TFields;
};
// ID information chunk
template <typename TBase, std::intmax_t TId>
class MessageImplStaticNumIdBase : public TBase {
public:
// Reuse the message ID type defined in the interface
using MsgIdType = typename TBase::MsgIdType;
protected:
virtual MsgIdType GetIdImpl() const override {
return static_cast<MsgIdType>(TId);
}
};
// Dispatch implementation chunk
template <typename TBase, typename TActual>
class MessageImplDispatchBase : public TBase {
public:
// Reuse the Handler type defined in the interface class
using Handler = typename TBase::HandlerType;
protected:
virtual void DispatchImpl(Handler& handler) override {
handler.Handle(static_cast<TActual&>(*this));
}
};
template <typename TBase, typename TFields>
class MessageImplFieldsBase : public TBase {
public:
using AllFields = TFields;
AllFields& GetFields() {
return m_Fields;
}
const AllFields& GetFields() const {
return m_Fields;
}
template<std::size_t FIndex>
auto& GetField() {
return std::get<FIndex>(GetFields()).GetValue();
}
private:
TFields m_Fields;
};
template <typename TBase>
class MessageImplFieldsReadBase : public TBase {
protected:
void ReadImpl(DataBuffer& buffer) override {
//TODO: add endianess
auto& allFields = TBase::GetFields();
tupleForEach(allFields, FieldReader{buffer});
}
};
template <typename TBase>
class MessageImplFieldsWriteBase : public TBase {
protected:
void WriteImpl(DataBuffer& buffer) override {
//TODO: add endianess
auto& allFields = TBase::GetFields();
tupleForEach(allFields, FieldWriter{buffer});
}
};
template <typename TBase>
class MessageImplFieldsValidBase : public TBase {
protected:
bool ValidImpl() const override {
// Access fields via interface provided in previous chunk
// auto& allFields = TBase::GetFields();
//... // validate all the fields
}
};
// id impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessStaticNumId;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessStaticNumId<TBase, ParsedImplOptions, true> {
using Type = MessageImplStaticNumIdBase<TBase, ParsedImplOptions::MsgId>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessStaticNumId<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// dispatch impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessDispatch;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessDispatch<TBase, ParsedImplOptions, true> {
using Type = MessageImplDispatchBase<TBase, typename ParsedImplOptions::ActualMessage>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessDispatch<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// fields impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessFields;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessFields<TBase, ParsedImplOptions, true> {
using Type = MessageImplFieldsBase<TBase, typename ParsedImplOptions::Fields>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessFields<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// read impl
template <typename TBase, bool TImplement>
struct MessageImplProcessReadFields;
template <typename TBase>
struct MessageImplProcessReadFields<TBase, true> {
using Type = MessageImplFieldsReadBase<TBase>;
};
template <typename TBase>
struct MessageImplProcessReadFields<TBase, false> {
using Type = TBase;
};
// write impl
template <typename TBase, bool TImplement>
struct MessageImplProcessWriteFields;
template <typename TBase>
struct MessageImplProcessWriteFields<TBase, true> {
using Type = MessageImplFieldsWriteBase<TBase>;
};
template <typename TBase>
struct MessageImplProcessWriteFields<TBase, false> {
using Type = TBase;
};
// valid impl
template <typename TBase, bool TImplement>
struct MessageImplProcessValidFields;
template <typename TBase>
struct MessageImplProcessValidFields<TBase, true> {
using Type = MessageImplFieldsValidBase<TBase>;
};
template <typename TBase>
struct MessageImplProcessValidFields<TBase, false> {
using Type = TBase;
};
// TBase is interface class
// TOptions... are the implementation options
template <typename TBase, typename... TOptions>
struct MessageImplBuilder {
// ParsedOptions class is supposed to be defined in comms::Message class
using InterfaceOptions = typename TBase::ParsedOptions;
// Parse implementation options
using ImplOptions = MessageImplParsedOptions<TOptions...>;
// Provide GetIdImpl() if possible
static const bool HasStaticNumIdImpl = InterfaceOptions::HasMsgIdType && ImplOptions::HasStaticNumIdImpl;
using Base1 = typename MessageImplProcessStaticNumId<TBase, ImplOptions, HasStaticNumIdImpl>::Type;
// Provide DispatchImpl() if possible
static const bool HasDispatchImpl = InterfaceOptions::HasHandler && ImplOptions::HasDispatchImpl;
using Base2 = typename MessageImplProcessDispatch<Base1, ImplOptions, HasDispatchImpl>::Type;
// Provide access to fields if possible
using Base3 = typename MessageImplProcessFields<Base2, ImplOptions, ImplOptions::HasFieldsImpl>::Type;
// Provide ReadImpl() if possible
static const bool HasReadImpl = InterfaceOptions::HasReadOperations && ImplOptions::HasFieldsImpl;
using Base4 = typename MessageImplProcessReadFields<Base3, HasReadImpl>::Type;
// Provide WriteImpl() if possible
static const bool HasWriteImpl = InterfaceOptions::HasWriteOperations && ImplOptions::HasFieldsImpl;
using Base5 = typename MessageImplProcessWriteFields<Base4, HasWriteImpl>::Type;
// Provide ValidImpl() if possible
static const bool HasValidImpl = InterfaceOptions::HasWriteOperations && ImplOptions::HasFieldsImpl;
using Base6 = typename MessageImplProcessValidFields<Base5, HasValidImpl>::Type;
// The last BaseN must be taken as final type.
using Type = Base6;
};
template <typename TBase, typename... TOptions>
class MessageBase : public MessageImplBuilder<TBase, TOptions...>::Type {};
} // namespace sp

View File

@@ -1,243 +0,0 @@
#pragma once
#include <sp/Options.h>
namespace sp {
namespace option {
template <typename... TOptions>
struct MessageInterfaceParsedOptions {};
template <>
struct MessageInterfaceParsedOptions<> {
static const bool HasMsgIdType = false;
static const bool HasLittleEndian = false;
static const bool HasReadOperations = false;
static const bool HasWriteOperations = false;
static const bool HasHandler = false;
static const bool HasValid = false;
};
template <typename T, typename... TOptions>
struct MessageInterfaceParsedOptions<MsgIdType<T>, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasMsgIdType = true;
using MsgIdType = T;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<LittleEndian, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasLittleEndian = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<ReadOperations, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasReadOperations = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<WriteOperations, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasWriteOperations = true;
};
template <typename T, typename... TOptions>
struct MessageInterfaceParsedOptions<Handler<T>, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasHandler = true;
using HandlerType = Handler<T>;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<ValidCheckInterface, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasValid = true;
};
// ID retrieval chunk
template <typename TBase, typename TId>
class MessageInterfaceIdTypeBase : public TBase {
public:
using MsgIdType = TId;
MsgIdType GetId() const {
return GetIdImpl();
}
protected:
virtual MsgIdType GetIdImpl() const = 0;
};
// Big endian serialisation chunk
template <typename TBase>
class MessageInterfaceBigEndian : public TBase {
protected:
template <typename T>
static T ReadData(DataBuffer& buffer) {
// use big endian
}
template <typename T>
static void WriteData(T value, DataBuffer& buffer) {
// use big endian
}
};
// Little endian serialisation chunk
template <typename TBase>
class MessageInterfaceLittleEndian : public TBase {
protected:
template <typename T>
static T ReadData(DataBuffer& buffer) {
// use little endian
}
template <typename T>
static void WriteData(const T& value, DataBuffer& buffer) {
// use little endian
}
};
// Read functionality chunk
template <typename TBase>
class MessageInterfaceReadBase : public TBase {
public:
void Read(DataBuffer& buffer) {
return ReadImpl(buffer);
}
protected:
virtual void ReadImpl(DataBuffer& buffer) = 0;
};
// Write functionality chunk
template <typename TBase>
class MessageInterfaceWriteBase : public TBase {
public:
void Write(DataBuffer& buffer) {
return WriteImpl(buffer);
}
protected:
virtual void WriteImpl(DataBuffer& buffer) = 0;
};
// Handler functionality chunk
template <typename TBase, typename THandler>
class MessageInterfaceHandlerBase : public TBase {
public:
using HandlerType = typename THandler::HandlerT;
void Dispatch(HandlerType& handler) {
DispatchImpl(handler);
}
protected:
virtual void DispatchImpl(HandlerType& handler) = 0;
};
// Validity functionality chunk
template <typename TBase>
class MessageInterfaceValidityBase : public TBase {
public:
bool Valid() const {
return ValidImpl();
}
protected:
virtual bool ValidImpl() const = 0;
};
// Build message Id
template <typename TBase, typename TParsedOptions, bool THasMsgIdType>
struct MessageInterfaceProcessMsgId;
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessMsgId<TBase, TParsedOptions, true> {
using Type = MessageInterfaceIdTypeBase<TBase, typename TParsedOptions::MsgIdType>;
};
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessMsgId<TBase, TParsedOptions, false> {
using Type = TBase;
};
// Build endianess
template <typename TBase, bool THasLittleEndian>
struct MessageInterfaceProcessEndian;
template <typename TBase>
struct MessageInterfaceProcessEndian<TBase, true> {
using Type = MessageInterfaceLittleEndian<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessEndian<TBase, false> {
using Type = MessageInterfaceBigEndian<TBase>;
};
// Build read
template <typename TBase, bool THasRead>
struct MessageInterfaceProcessRead;
template <typename TBase>
struct MessageInterfaceProcessRead<TBase, true> {
using Type = MessageInterfaceReadBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessRead<TBase, false> {
using Type = TBase;
};
// Build write
template <typename TBase, bool THasWrite>
struct MessageInterfaceProcessWrite;
template <typename TBase>
struct MessageInterfaceProcessWrite<TBase, true> {
using Type = MessageInterfaceWriteBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessWrite<TBase, false> {
using Type = TBase;
};
// Build handler
template <typename TBase, typename TParsedOptions, bool THasHandler>
struct MessageInterfaceProcessHandler;
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessHandler<TBase, TParsedOptions, true> {
using Type = MessageInterfaceHandlerBase<TBase, typename TParsedOptions::HandlerType>;
};
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessHandler<TBase, TParsedOptions, false> {
using Type = TBase;
};
// Build valid
template <typename TBase, bool THasValid>
struct MessageInterfaceProcessValid;
template <typename TBase>
struct MessageInterfaceProcessValid<TBase, true> {
using Type = MessageInterfaceValidityBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessValid<TBase, false> {
using Type = TBase;
};
} // namespace option
} // namespace sp

View File

@@ -1,86 +0,0 @@
#pragma once
#include <cstdint>
namespace sp {
/// @brief Default case, see field_index specialization for implementation details
template <size_t N, typename T, template <size_t, typename> typename U, typename = void>
static constexpr size_t field_index = 0;
/// @brief A templated size_t that counts the number of existing classes U with a "field_name" member
/// @tparam N Current counter for recursion (user should always call it with 0)
/// @tparam T Can be any type, must be different for each field we want to be counted later (used because we can't have a empty
/// template<> specialization nested in a class)
/// @tparam U The templated class that will be searched for match
template <size_t N, typename T, template <size_t, typename> typename U>
static constexpr size_t field_index<N, T, U, std::void_t<decltype(U<N, T>::field_name)>> = 1 + field_index<N + 1, T, U>;
/// @brief Concat multiple tuples in one big tuple
/// @tparam ...input_t Multiple std::tuple types to concat
template <typename... input_t>
using tuple_cat_t = decltype(std::tuple_cat(std::declval<input_t>()...));
template <typename T, typename Tuple>
constexpr bool tuple_contains_type = false;
template <typename T, typename... Ts>
constexpr bool tuple_contains_type<T, std::tuple<Ts...>> = std::disjunction_v<std::is_same<T, Ts>...>;
template <typename T, typename Tuple>
constexpr int get_tuple_index = 0;
template <typename T, typename... Rest>
constexpr int get_tuple_index<T, std::tuple<T, Rest...>> = 0;
template <typename T, typename First, typename... Rest>
constexpr int get_tuple_index<T, std::tuple<First, Rest...>> = 1 + get_tuple_index<T, std::tuple<Rest...>>;
// Template black magic to loop at compile time
template <std::size_t... indices, class LoopBody>
void loop_impl(std::index_sequence<indices...>, LoopBody&& loop_body) {
(loop_body(std::integral_constant<std::size_t, indices>{}), ...);
}
template <std::size_t N, class LoopBody>
void loop(LoopBody&& loop_body) {
loop_impl(std::make_index_sequence<N>{}, std::forward<LoopBody>(loop_body));
}
namespace details {
template <std::size_t TRem>
class TupleForEachHelper {
public:
template <typename TTuple, typename TFunc>
static void exec(TTuple&& tuple, TFunc&& func) {
using Tuple = typename std::decay<TTuple>::type;
static const std::size_t TupleSize = std::tuple_size<Tuple>::value;
static_assert(TRem <= TupleSize, "Incorrect parameters");
// Invoke function with current element
static const std::size_t Idx = TupleSize - TRem;
func(std::get<Idx>(std::forward<TTuple>(tuple)));
// Compile time recursion - invoke function with the remaining elements
TupleForEachHelper<TRem - 1>::exec(std::forward<TTuple>(tuple), std::forward<TFunc>(func));
}
};
template <>
class TupleForEachHelper<0> {
public:
// Stop compile time recursion
template <typename TTuple, typename TFunc>
static void exec(TTuple&& tuple, TFunc&& func) {
static_cast<void>(tuple);
static_cast<void>(func);
}
};
} // namespace details
template <typename TTuple, typename TFunc>
void tupleForEach(TTuple&& tuple, TFunc&& func) {
using Tuple = typename std::decay<TTuple>::type;
static const std::size_t TupleSize = std::tuple_size<Tuple>::value;
details::TupleForEachHelper<TupleSize>::exec(std::forward<TTuple>(tuple), std::forward<TFunc>(func));
}
} // namespace sp

View File

@@ -1,9 +0,0 @@
#pragma once
#include <cstdint>
namespace sp {
using PeerID = std::uint16_t;
} // namespace sp

View File

@@ -0,0 +1,92 @@
#pragma once
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <cstdint>
namespace sp {
template <typename T>
void SwapBytes(T& value) {
char* ptr = reinterpret_cast<char*>(&value);
std::reverse(ptr, ptr + sizeof(T));
}
bool IsSystemBigEndian() {
static constexpr std::uint16_t test = 10;
static const bool isBigEndian = reinterpret_cast<const std::uint8_t*>(&test)[1] == 10;
return isBigEndian;
}
/**
* \brief Serialize value to (network byte order) big endian
*/
template <typename T>
void ToNetwork(T& value) {}
template <>
void ToNetwork<std::uint16_t>(std::uint16_t& value) {
value = htons(value);
}
template <>
void ToNetwork<std::uint32_t>(std::uint32_t& value) {
value = htonl(value);
}
template <>
void ToNetwork<std::uint64_t>(std::uint64_t& value) {
if (IsSystemBigEndian())
return;
SwapBytes(value);
}
/**
* \brief Deserialize value from (network byte order) big endian
*/
template <typename T>
void FromNetwork(T& value) {}
template <>
void FromNetwork<std::uint16_t>(std::uint16_t& value) {
value = ntohs(value);
}
template <>
void FromNetwork<std::uint32_t>(std::uint32_t& value) {
value = ntohl(value);
}
template <>
void FromNetwork<std::uint64_t>(std::uint64_t& value) {
if (IsSystemBigEndian())
return;
SwapBytes(value);
}
/**
* \brief Swap bytes if the value is any kind of integer
*/
template <typename T>
void TrySwapBytes(T& value) {}
template <>
void TrySwapBytes<std::uint16_t>(std::uint16_t& value) {
SwapBytes(value);
}
template <>
void TrySwapBytes<std::uint32_t>(std::uint32_t& value) {
SwapBytes(value);
}
template <>
void TrySwapBytes<std::uint64_t>(std::uint64_t& value) {
SwapBytes(value);
}
} // namespace sp

View File

@@ -13,6 +13,7 @@
#include <string> #include <string>
#include <sp/common/VarInt.h> #include <sp/common/VarInt.h>
#include <vector> #include <vector>
#include <map>
namespace sp { namespace sp {
@@ -88,6 +89,19 @@ class DataBuffer {
return *this; return *this;
} }
/**
* \brief Append a map to the buffer by first writing the size
* \param data The map to append
*/
template <typename K, typename V>
DataBuffer& operator<<(const std::map<K, V>& data) {
*this << VarInt{data.size()};
for (const auto& element : data) {
*this << element.first << element.second;
}
return *this;
}
/** /**
* \brief Append an array to the buffer by first writing the size * \brief Append an array to the buffer by first writing the size
* \param data The buffer to append * \param data The buffer to append
@@ -140,6 +154,23 @@ class DataBuffer {
return *this; return *this;
} }
/**
* \brief Read a map (size + data) from the buffer
* \pre The map is assumed to be empty
*/
template <typename K, typename V>
DataBuffer& operator>>(std::map<K, V>& data) {
VarInt mapSize;
*this >> mapSize;
for (std::size_t i = 0; i < mapSize.GetValue(); i++) {
K newKey;
V newValue;
*this >> newKey >> newValue;
data.insert({newKey, newValue});
}
return *this;
}
/** /**
* \brief Read an array from the buffer * \brief Read an array from the buffer
*/ */
@@ -261,13 +292,13 @@ class DataBuffer {
* \brief Read a file into the buffer * \brief Read a file into the buffer
* \param fileName The name of the file to read * \param fileName The name of the file to read
*/ */
bool ReadFile(const std::string& fileName); void ReadFile(const std::string& fileName);
/** /**
* \brief Write a file into the buffer * \brief Write a file into the buffer
* \param fileName The name of the file to write to * \param fileName The name of the file to write to
*/ */
bool WriteFile(const std::string& fileName) const; void WriteFile(const std::string& fileName) const;
/** /**
* \brief Allocate the buffer on the heap * \brief Allocate the buffer on the heap

View File

@@ -1,54 +0,0 @@
/*
* Created by William Swanson in 2012.
*
* I, William Swanson, dedicate this work to the public domain.
* I waive all rights to the work worldwide under copyright law,
* including all related and neighboring rights,
* to the extent allowed by law.
*
* You can copy, modify, distribute and perform the work,
* even for commercial purposes, all without asking permission.
*/
#ifndef MAP_H_INCLUDED
#define MAP_H_INCLUDED
#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__)))
#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define MAP_END(...)
#define MAP_OUT
#define MAP_COMMA ,
#define MAP_GET_END2() 0, MAP_END
#define MAP_GET_END1(...) MAP_GET_END2
#define MAP_GET_END(...) MAP_GET_END1
#define MAP_NEXT0(test, next, ...) next MAP_OUT
#define MAP_NEXT1(test, next) MAP_NEXT0(test, next, 0)
#define MAP_NEXT(test, next) MAP_NEXT1(MAP_GET_END test, next)
#define MAP0(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP1)(f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP0)(f, peek, __VA_ARGS__)
#define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0)
#define MAP_LIST_NEXT(test, next) MAP_LIST_NEXT1(MAP_GET_END test, next)
#define MAP_LIST0(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, peek, __VA_ARGS__)
#define MAP_LIST1(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, peek, __VA_ARGS__)
/**
* Applies the function macro `f` to each of the remaining parameters.
*/
#define MAP(f, ...) EVAL(MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
/**
* Applies the function macro `f` to each of the remaining parameters and
* inserts commas between the results.
*/
#define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
#endif

View File

@@ -1,25 +0,0 @@
#pragma once
/**
* \file NonCopyable.h
* \brief File containing the sp::NonCopyable class
*/
namespace sp {
/**
* \class NonCopyable
* \brief Class used to make a class non copyable
* \note Inherit from this class privately to make a class non copyable
*/
class NonCopyable {
public:
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
protected:
NonCopyable() {}
~NonCopyable() {}
};
} // namespace sp

View File

@@ -0,0 +1,53 @@
#pragma once
#include <cstdint>
namespace sp {
/// @brief Concat multiple tuples in one big tuple
/// @tparam ...input_t Multiple std::tuple types to concat
template <typename... input_t>
using tuple_cat_t = decltype(std::tuple_cat(std::declval<input_t>()...));
namespace details {
template <std::size_t TRem>
class TupleForEachHelper {
public:
template <typename TTuple, typename TFunc>
static void Exec(TTuple&& tuple, TFunc&& func) {
using Tuple = typename std::decay<TTuple>::type;
static const std::size_t TupleSize = std::tuple_size<Tuple>::value;
static_assert(TRem <= TupleSize, "Incorrect parameters");
// Invoke function with current element
static const std::size_t Idx = TupleSize - TRem;
func(std::get<Idx>(std::forward<TTuple>(tuple)));
// Compile time recursion - invoke function with the remaining elements
TupleForEachHelper<TRem - 1>::Exec(std::forward<TTuple>(tuple), std::forward<TFunc>(func));
}
};
template <>
class TupleForEachHelper<0> {
public:
// Stop compile time recursion
template <typename TTuple, typename TFunc>
static void Exec(TTuple&& tuple, TFunc&& func) {
static_cast<void>(tuple);
static_cast<void>(func);
}
};
} // namespace details
template <typename TFunc, typename TTuple>
void TupleForEach(TFunc&& func, TTuple&& tuple) {
using Tuple = typename std::decay<TTuple>::type;
static const std::size_t TupleSize = std::tuple_size<Tuple>::value;
details::TupleForEachHelper<TupleSize>::Exec(std::forward<TTuple>(tuple), std::forward<TFunc>(func));
}
} // namespace sp

View File

@@ -0,0 +1,30 @@
#pragma once
#include <sp/protocol/GenericHandler.h>
#include <sp/protocol/Message.h>
namespace sp {
class PacketHandler;
using PacketMessage = Message<
option::MsgIdType<std::uint8_t>, // add id() operation
option::ReadOperations, // add read() operation
option::WriteOperations, // add write() operation
option::WriteId, // write id before data
option::Handler<PacketHandler> // add dispatch() operation
>;
#define PacketConstructor(packetName) \
packetName##Packet() {} \
template <typename... Args> \
packetName##Packet(Args... args) { \
Construct(args...); \
}
#define DeclarePacket(packetName) \
class packetName##Packet : public sp::MessageBase<sp::PacketMessage, sp::option::StaticNumIdImpl<packetName>, \
sp::option::DispatchImpl<packetName##Packet>, sp::option::FieldsImpl<packetName##Fields>>
} // namespace sp

View File

@@ -0,0 +1,7 @@
#pragma once
#include <sp/protocol/MessageFactory.h>
namespace sp {
using PacketFactory = sp::MessageFactory<sp::PacketMessage, AllPackets>;
} // namespace sp

View File

@@ -0,0 +1,9 @@
#pragma once
#include <sp/protocol/GenericHandler.h>
// the tuple AllPackets must be defined !
namespace sp {
class PacketHandler : public sp::GenericHandler<PacketMessage, AllPackets> {};
} // namespace sp

View File

@@ -1,38 +0,0 @@
#pragma once
/**
* \file Format.h
* \brief This file contains the definition of the `Format` function.
*/
#include <memory>
#include <stdexcept>
#include <string>
namespace sp {
namespace utils {
/**
* \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 utils
} // namespace sp

View File

@@ -1,32 +0,0 @@
#pragma once
/**
* \file Log.h
* \brief File defining log functions
*/
#include <string>
namespace sp {
namespace utils {
/**
* \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 utils
} // namespace sp

View File

@@ -1,70 +0,0 @@
#pragma once
/**
* \file Test.h
* \brief File containing unit testing utilities
*/
#include <cstdlib>
#include <sp/misc/Log.h>
namespace sp {
namespace test {
/**
* \def SP_TEST_SUCCESSFUL
* \brief Used in tests to indicate that a test was successful
*/
#define SP_TEST_SUCCESSFUL 0
/**
* \def SP_TEST_FAILED
* \brief Used in tests to indicate that a test failed
*/
#define SP_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 sp_test_assert(...) \
if (!static_cast<bool>(__VA_ARGS__)) { \
td::test::LogAssert(#__VA_ARGS__, __FILE__, __LINE__, __FUNCTION_NAME__); \
std::exit(SP_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 sp_debug_assert(...) __VA_ARGS__
#else
#define sp_debug_assert sp_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 sp

119
include/sp/protocol/Field.h Normal file
View File

@@ -0,0 +1,119 @@
#pragma once
#include <sp/common/DataBuffer.h>
#include <sp/common/Templates.h>
namespace sp {
/**
* \brief Example usage :
* sp::BitField<std::uint16_t, sp::Field<std::uint16_t, 12>, sp::Field<std::uint8_t, 4>>;
*/
template <typename TContainer, typename... TFields>
class BitField {
using AllFields = std::tuple<TFields...>;
public:
template <typename... T>
BitField(const std::tuple<T...>& args) {
Apply<0, T...>(args);
}
BitField() {}
template <typename... T>
BitField& operator=(const std::tuple<T...>& args) {
Apply<0, T...>(args);
return *this;
}
AllFields& GetFields() {
return m_Fields;
}
const AllFields& GetFields() const {
return m_Fields;
}
template <std::size_t FIndex>
auto& GetField() {
return std::get<FIndex>(this->GetFields()).GetValue();
}
template <std::size_t FIndex>
const auto& GetField() const {
return std::get<FIndex>(this->GetFields()).GetValue();
}
// allow use of enums
template <typename E, E FIndex>
const auto& GetField() const {
return std::get<static_cast<std::size_t>(FIndex)>(this->GetFields()).GetValue();
}
private:
template <int IOffset, typename... T, std::enable_if_t<IOffset >= sizeof...(T), bool> = true>
void Apply(const std::tuple<T...>& args) {}
template <int IOffset, typename... T, std::enable_if_t<!(IOffset >= sizeof...(T)), bool> = true>
void Apply(const std::tuple<T...>& args) {
this->GetField<IOffset>() = std::get<IOffset>(args);
Apply<1 + IOffset, T...>(args);
}
TContainer m_Value;
AllFields m_Fields;
};
/**
*
* \tparam ValueType the type of the value to store
* \tparam IAlignment 0 means no alignment
*/
template <typename ValueType, int IAlignment>
class Field {
public:
// Provide an access to the stored value
ValueType& GetValue() {
return m_Value;
}
const ValueType& GetValue() const {
return m_Value;
}
Field& operator=(const ValueType& value) {
m_Value = value;
return *this;
}
constexpr int GetAlignment() const {
return IAlignment;
}
private:
ValueType m_Value;
};
namespace details {
template <typename... TFields>
struct FieldsBuilder {};
template <>
struct FieldsBuilder<> {
using Type = std::tuple<>;
};
template <typename... TFields>
struct FieldsBuilder<std::tuple<TFields...>> {
using Type = typename FieldsBuilder<TFields...>::Type;
};
template <typename TField, typename... TFields>
struct FieldsBuilder<TField, TFields...> {
using Type = sp::tuple_cat_t<std::tuple<Field<TField, 0>>, typename FieldsBuilder<TFields...>::Type>;
};
} // namespace details
} // namespace sp

View File

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

View File

@@ -0,0 +1,16 @@
#pragma once
// Inspired by
// https://alex-robenko.gitbook.io/comms-protocols-cpp
#include <sp/protocol/message/MessageInterfaceBuilder.h>
namespace sp {
template <typename... TOptions>
class Message : public details::MessageInterfaceBuilder<TOptions...>::Type {
public:
using ParsedOptions = typename details::MessageInterfaceBuilder<TOptions...>::ParsedOptions;
};
} // namespace sp

View File

@@ -0,0 +1,16 @@
#pragma once
#include <sp/protocol/Message.h>
#include <sp/protocol/message/MessageImplOptions.h>
#include <sp/protocol/message/MessagesImpl.h>
#include <sp/protocol/message/MessageImplProcess.h>
#include <sp/protocol/message/MessageImplBuilder.h>
namespace sp {
template <typename TBase, typename... TOptions>
class MessageBase : public details::MessageImplBuilder<TBase, TOptions...>::Type {};
} // namespace sp

View File

@@ -0,0 +1,66 @@
#pragma once
#include <array>
#include <functional>
#include <iostream>
#include <memory>
namespace sp {
namespace details {
template <typename TBase>
using ArrayType = std::vector<std::function<std::unique_ptr<TBase>(void)>>;
template <typename TBase, typename... TMessages>
struct ArrayFiller {};
template <typename TBase, typename... TMessages>
struct ArrayFiller<TBase, std::tuple<TMessages...>> {
static ArrayType<TBase> ArrayCreate() {
ArrayType<TBase> array;
array.reserve(sizeof...(TMessages));
ArrayFiller<TBase, TMessages...>::ArrayAppend(array);
return array;
}
};
template <typename TBase, typename TMessage, typename... TMessages>
struct ArrayFiller<TBase, TMessage, TMessages...> {
static void ArrayAppend(details::ArrayType<TBase>& array) {
ArrayFiller<TBase, TMessage>::ArrayAppend(array);
ArrayFiller<TBase, TMessages...>::ArrayAppend(array);
}
};
template <typename TBase, typename TMessage>
struct ArrayFiller<TBase, TMessage> {
static void ArrayAppend(details::ArrayType<TBase>& array) {
array.push_back([]() -> std::unique_ptr<TBase> { return std::make_unique<TMessage>(); });
}
};
} // namespace details
template <typename TBase, typename TTMessages>
class MessageFactory {
public:
using IdType = typename TBase::MsgIdType;
MessageFactory() : m_Factory(details::ArrayFiller<TBase, TTMessages>::ArrayCreate()) {}
std::unique_ptr<TBase> CreateMessage(IdType id) {
if (id >= m_Factory.size())
return nullptr;
return m_Factory.at(id)();
}
private:
details::ArrayType<TBase> m_Factory;
};
} // namespace sp

View File

@@ -0,0 +1,45 @@
#pragma once
namespace sp {
namespace details {
// TBase is interface class
// TOptions... are the implementation options
template <typename TBase, typename... TOptions>
struct MessageImplBuilder {
// ParsedOptions class is supposed to be defined in comms::Message class
using InterfaceOptions = typename TBase::ParsedOptions;
// Parse implementation options
using ImplOptions = MessageImplParsedOptions<TOptions...>;
// Provide GetIdImpl() if possible
static const bool HasStaticNumIdImpl = InterfaceOptions::HasMsgIdType && ImplOptions::HasStaticNumIdImpl;
using Base1 = typename MessageImplProcessStaticNumId<TBase, ImplOptions, HasStaticNumIdImpl>::Type;
// Provide DispatchImpl() if possible
static const bool HasDispatchImpl = InterfaceOptions::HasHandler && ImplOptions::HasDispatchImpl;
using Base2 = typename MessageImplProcessDispatch<Base1, ImplOptions, HasDispatchImpl>::Type;
// Provide access to fields if possible
using Base3 = typename MessageImplProcessFields<Base2, ImplOptions, ImplOptions::HasFieldsImpl>::Type;
// Provide ReadImpl() if possible
static const bool HasReadImpl = InterfaceOptions::HasReadOperations && ImplOptions::HasFieldsImpl;
using Base4 = typename MessageImplProcessReadFields<Base3, HasReadImpl>::Type;
// Provide WriteImpl() if possible
static const bool HasWriteImpl = InterfaceOptions::HasWriteOperations && ImplOptions::HasFieldsImpl;
using Base5 = typename MessageImplProcessWriteFields<Base4, HasWriteImpl>::Type;
// Provide ValidImpl() if possible
static const bool HasValidImpl = InterfaceOptions::HasValid && ImplOptions::HasFieldsImpl;
using Base6 = typename MessageImplProcessValidFields<Base5, HasValidImpl>::Type;
// The last BaseN must be taken as final type.
using Type = Base6;
};
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,61 @@
#pragma once
namespace sp {
namespace option {
// Provide static numeric ID, to facilitate implementation of GetIdImpl()
template <std::intmax_t TId>
struct StaticNumIdImpl {};
// Facilitate implementation of DispatchImpl()
template <typename TActual>
struct DispatchImpl {};
// Provide fields of the message, facilitate implementation of
// ReadImpl(), WriteImpl(), ValidImpl(), etc...
template <typename TFields>
struct FieldsImpl {};
} // namespace option
namespace details {
template <typename... TOptions>
class MessageImplParsedOptions;
template <>
struct MessageImplParsedOptions<> {
static const bool HasStaticNumIdImpl = false;
static const bool HasDispatchImpl = false;
static const bool HasFieldsImpl = false;
};
template <std::intmax_t TId, typename... TOptions>
struct MessageImplParsedOptions<option::StaticNumIdImpl<TId>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasStaticNumIdImpl = true;
static const std::intmax_t MsgId = TId;
};
template <typename TActual, typename... TOptions>
struct MessageImplParsedOptions<option::DispatchImpl<TActual>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasDispatchImpl = true;
using ActualMessage = TActual;
};
template <typename TFields, typename... TOptions>
struct MessageImplParsedOptions<option::FieldsImpl<TFields>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasFieldsImpl = true;
using Fields = TFields;
};
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,93 @@
#pragma once
namespace sp {
namespace details {
// id impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessStaticNumId;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessStaticNumId<TBase, ParsedImplOptions, true> {
using Type = MessageImplStaticNumIdBase<TBase, ParsedImplOptions::MsgId>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessStaticNumId<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// dispatch impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessDispatch;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessDispatch<TBase, ParsedImplOptions, true> {
using Type = MessageImplDispatchBase<TBase, typename ParsedImplOptions::ActualMessage>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessDispatch<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// fields impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessFields;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessFields<TBase, ParsedImplOptions, true> {
using Type = MessageImplFieldsBase<TBase, typename ParsedImplOptions::Fields>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessFields<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// read impl
template <typename TBase, bool TImplement>
struct MessageImplProcessReadFields;
template <typename TBase>
struct MessageImplProcessReadFields<TBase, true> {
using Type = MessageImplFieldsReadBase<TBase>;
};
template <typename TBase>
struct MessageImplProcessReadFields<TBase, false> {
using Type = TBase;
};
// write impl
template <typename TBase, bool TImplement>
struct MessageImplProcessWriteFields;
template <typename TBase>
struct MessageImplProcessWriteFields<TBase, true> {
using Type = MessageImplFieldsWriteBase<TBase>;
};
template <typename TBase>
struct MessageImplProcessWriteFields<TBase, false> {
using Type = TBase;
};
// valid impl
template <typename TBase, bool TImplement>
struct MessageImplProcessValidFields;
template <typename TBase>
struct MessageImplProcessValidFields<TBase, true> {
using Type = MessageImplFieldsValidBase<TBase>;
};
template <typename TBase>
struct MessageImplProcessValidFields<TBase, false> {
using Type = TBase;
};
} // namespace details
} // namespace sp

View File

@@ -1,9 +1,9 @@
#pragma once #pragma once
#include <sp/MessageInterfaces.h> #include <sp/protocol/message/MessageInterfaces.h>
namespace sp { namespace sp {
namespace option { namespace details {
class EmptyBase {}; class EmptyBase {};
@@ -30,9 +30,12 @@ struct MessageInterfaceBuilder {
// add valid functionality if Valid tpe was provided // add valid functionality if Valid tpe was provided
using Base6 = typename MessageInterfaceProcessValid<Base5, ParsedOptions::HasValid>::Type; using Base6 = typename MessageInterfaceProcessValid<Base5, ParsedOptions::HasValid>::Type;
// The last Base6 must be taken as final type. // add write id functionality if write id and write was provided
using Type = Base6; using Base7 = typename MessageInterfaceProcessWriteId<Base6, ParsedOptions::HasWriteId && ParsedOptions::HasWriteOperations>::Type;
// The last Base7 must be taken as final type.
using Type = Base7;
}; };
} // namespace option } // namespace details
} // namespace sp } // namespace sp

View File

@@ -0,0 +1,105 @@
#pragma once
namespace sp {
namespace details {
// Build message Id
template <typename TBase, typename TParsedOptions, bool THasMsgIdType>
struct MessageInterfaceProcessMsgId;
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessMsgId<TBase, TParsedOptions, true> {
using Type = MessageInterfaceIdTypeBase<TBase, typename TParsedOptions::MsgIdType>;
};
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessMsgId<TBase, TParsedOptions, false> {
using Type = TBase;
};
// Build endianess
template <typename TBase, bool THasLittleEndian>
struct MessageInterfaceProcessEndian;
template <typename TBase>
struct MessageInterfaceProcessEndian<TBase, true> {
using Type = MessageInterfaceLittleEndian<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessEndian<TBase, false> {
using Type = MessageInterfaceBigEndian<TBase>;
};
// Build read
template <typename TBase, bool THasRead>
struct MessageInterfaceProcessRead;
template <typename TBase>
struct MessageInterfaceProcessRead<TBase, true> {
using Type = MessageInterfaceReadBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessRead<TBase, false> {
using Type = TBase;
};
// Build write
template <typename TBase, bool THasWrite>
struct MessageInterfaceProcessWrite;
template <typename TBase>
struct MessageInterfaceProcessWrite<TBase, true> {
using Type = MessageInterfaceWriteBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessWrite<TBase, false> {
using Type = TBase;
};
// Build handler
template <typename TBase, typename TParsedOptions, bool THasHandler>
struct MessageInterfaceProcessHandler;
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessHandler<TBase, TParsedOptions, true> {
using Type = MessageInterfaceHandlerBase<TBase, typename TParsedOptions::HandlerType>;
};
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessHandler<TBase, TParsedOptions, false> {
using Type = TBase;
};
// Build valid
template <typename TBase, bool THasValid>
struct MessageInterfaceProcessValid;
template <typename TBase>
struct MessageInterfaceProcessValid<TBase, true> {
using Type = MessageInterfaceValidityBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessValid<TBase, false> {
using Type = TBase;
};
// Build id writing
template <typename TBase, bool THasValid>
struct MessageInterfaceProcessWriteId;
template <typename TBase>
struct MessageInterfaceProcessWriteId<TBase, true> {
using Type = MessageInterfaceWriteIdBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessWriteId<TBase, false> {
using Type = TBase;
};
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,7 @@
#pragma once
#include <sp/protocol/message/MessageOptions.h>
#include <sp/protocol/message/MessageInterfacesOptions.h>
#include <sp/protocol/message/MessageInterfacesImpl.h>
#include <sp/protocol/message/MessageInterfaceProcess.h>

View File

@@ -0,0 +1,119 @@
#pragma once
#include <sp/common/ByteSwapping.h>
namespace sp {
namespace details {
// ID retrieval chunk
template <typename TBase, typename TId>
class MessageInterfaceIdTypeBase : public TBase {
public:
using MsgIdType = TId;
MsgIdType GetId() const {
return GetIdImpl();
}
protected:
virtual MsgIdType GetIdImpl() const = 0;
};
// Big endian serialisation chunk
template <typename TBase>
class MessageInterfaceBigEndian : public TBase {
protected:
template <typename T>
void ReadData(T& value, DataBuffer& buffer) {
buffer >> value;
FromNetwork(value);
}
template <typename T>
void WriteData(T value, DataBuffer& buffer) {
ToNetwork(value);
buffer << value;
}
};
// Little endian serialisation chunk
template <typename TBase>
class MessageInterfaceLittleEndian : public TBase {
protected:
template <typename T>
void ReadData(T& value, DataBuffer& buffer) {
buffer >> value;
TrySwapBytes(value);
FromNetwork(value);
}
template <typename T>
void WriteData(const T& value, DataBuffer& buffer) {
ToNetwork(value);
TrySwapBytes(value);
buffer << value;
}
};
// Read functionality chunk
template <typename TBase>
class MessageInterfaceReadBase : public TBase {
public:
void Read(DataBuffer& buffer) {
return ReadImpl(buffer);
}
protected:
virtual void ReadImpl(DataBuffer& buffer) = 0;
};
// Write functionality chunk
template <typename TBase>
class MessageInterfaceWriteBase : public TBase {
public:
void Write(DataBuffer& buffer) {
WriteImpl(buffer);
}
protected:
virtual void WriteImpl(DataBuffer& buffer) = 0;
};
// Handler functionality chunk
template <typename TBase, typename THandler>
class MessageInterfaceHandlerBase : public TBase {
public:
using HandlerType = typename THandler::HandlerT;
void Dispatch(HandlerType& handler) {
DispatchImpl(handler);
}
protected:
virtual void DispatchImpl(HandlerType& handler) = 0;
};
// Validity functionality chunk
template <typename TBase>
class MessageInterfaceValidityBase : public TBase {
public:
bool Valid() const {
return ValidImpl();
}
protected:
virtual bool ValidImpl() const = 0;
};
// Writing id functionality chunk
template <typename TBase>
class MessageInterfaceWriteIdBase : public TBase {
public:
void Write(DataBuffer& buffer) {
this->WriteData(this->GetId(), buffer);
this->WriteImpl(buffer);
}
};
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,63 @@
#pragma once
namespace sp {
namespace details {
template <typename... TOptions>
struct MessageInterfaceParsedOptions {};
template <>
struct MessageInterfaceParsedOptions<> {
static const bool HasMsgIdType = false;
static const bool HasLittleEndian = false;
static const bool HasReadOperations = false;
static const bool HasWriteOperations = false;
static const bool HasWriteId = false;
static const bool HasHandler = false;
static const bool HasValid = false;
};
template <typename T, typename... TOptions>
struct MessageInterfaceParsedOptions<option::MsgIdType<T>, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasMsgIdType = true;
using MsgIdType = T;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::LittleEndian, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasLittleEndian = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::ReadOperations, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasReadOperations = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::WriteOperations, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasWriteOperations = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::WriteId, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasWriteId = true;
};
template <typename T, typename... TOptions>
struct MessageInterfaceParsedOptions<option::Handler<T>, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasHandler = true;
using HandlerType = option::Handler<T>;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::ValidCheckInterface, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasValid = true;
};
} // namespace details
} // namespace sp

View File

@@ -15,6 +15,9 @@ struct ReadOperations {};
// Enable writing // Enable writing
struct WriteOperations {}; struct WriteOperations {};
// Enable id writing
struct WriteId {};
// Use little endian for serialisation (instead of default big) // Use little endian for serialisation (instead of default big)
struct LittleEndian {}; struct LittleEndian {};

View File

@@ -0,0 +1,160 @@
#pragma once
namespace sp {
namespace details {
// ID information chunk
template <typename TBase, std::intmax_t TId>
class MessageImplStaticNumIdBase : public TBase {
public:
// Reuse the message ID type defined in the interface
using MsgIdType = typename TBase::MsgIdType;
protected:
virtual MsgIdType GetIdImpl() const override {
return static_cast<MsgIdType>(TId);
}
};
// Dispatch implementation chunk
template <typename TBase, typename TActual>
class MessageImplDispatchBase : public TBase {
public:
// Reuse the Handler type defined in the interface class
using Handler = typename TBase::HandlerType;
protected:
virtual void DispatchImpl(Handler& handler) override {
handler.Handle(static_cast<TActual&>(*this));
}
};
template <typename TBase, typename TFields>
class MessageImplFieldsBase : public TBase {
public:
using AllFields = typename details::FieldsBuilder<TFields>::Type;
template <typename... Args>
void Construct(Args... args) {
m_Fields = std::make_tuple(args...);
}
AllFields& GetFields() {
return m_Fields;
}
const AllFields& GetFields() const {
return m_Fields;
}
template <std::size_t FIndex>
auto& GetField() {
return std::get<FIndex>(GetFields()).GetValue();
}
template <std::size_t FIndex>
const auto& GetField() const {
return std::get<FIndex>(GetFields()).GetValue();
}
// allow use of enums
template <typename E, E FIndex>
const auto& GetField() const {
return std::get<static_cast<std::size_t>(FIndex)>(this->GetFields()).GetValue();
}
private:
AllFields m_Fields;
};
template <typename TBase>
class MessageImplFieldsReadBase : public TBase {
private:
// normal reading
template <typename TField>
void ReadField(Field<TField, 0>& field, DataBuffer& buffer) {
this->ReadData(field.GetValue(), buffer);
}
// reading field in bitfield
template <typename TFieldType, typename TField, int IAlignment>
void ReadField(Field<TField, IAlignment>& field, TFieldType data, std::size_t offset) {
static constexpr std::size_t TotalBitCount = sizeof(TFieldType) * 8;
// we suppose that the first element is at the highest bits
field.GetValue() = (data >> TotalBitCount - IAlignment - offset) & ((1 << IAlignment) - 1);
}
// reading bitfield
template <typename TContainer, typename TFirst, typename... TFields>
void ReadField(Field<BitField<TContainer, TFirst, TFields...>, 0>& field, DataBuffer& buffer) {
TContainer data;
this->ReadData(data, buffer);
std::size_t offset = 0;
TupleForEach(
[data, this, &offset](auto& field) {
this->ReadField(field, data, offset);
offset += field.GetAlignment();
},
field.GetValue().GetFields());
}
void ReadImpl(DataBuffer& buffer) override {
auto& allFields = this->GetFields();
TupleForEach([&buffer, this](auto& field) { this->ReadField(field, buffer); }, allFields);
}
};
template <typename TBase>
class MessageImplFieldsWriteBase : public TBase {
private:
// normal writing
template <typename TField>
void WriteField(Field<TField, 0>& field, DataBuffer& buffer) {
this->WriteData(field.GetValue(), buffer);
}
// writing field in bitfield
template <typename TFieldType, typename TField, int IAlignment>
void WriteField(Field<TField, IAlignment>& field, TFieldType& data, std::size_t offset) {
static constexpr std::size_t TotalBitCount = sizeof(TFieldType) * 8;
// we suppose that the first element is at the highest bits
data |= (field.GetValue() & ((1 << IAlignment) - 1)) << TotalBitCount - IAlignment - offset;
}
// writing bitfield
template <typename TContainer, typename TFirst, typename... TFields>
void WriteField(Field<BitField<TContainer, TFirst, TFields...>, 0>& field, DataBuffer& buffer) {
TContainer data = 0;
std::size_t offset = 0;
TupleForEach(
[&data, this, &offset](auto& field) {
this->WriteField(field, data, offset);
offset += field.GetAlignment();
},
field.GetValue().GetFields());
this->WriteData(data, buffer);
}
void WriteImpl(DataBuffer& buffer) override {
auto& allFields = this->GetFields();
TupleForEach([&buffer, this](auto& field) { this->WriteField(field, buffer); }, allFields);
}
};
template <typename TBase>
class MessageImplFieldsValidBase : public TBase {
protected:
bool ValidImpl() const override {
// Access fields via interface provided in previous chunk
// auto& allFields = TBase::GetFields();
//... // validate all the fields
return true;
}
};
} // namespace details
} // namespace sp

View File

@@ -1,70 +0,0 @@
#include <iostream>
#include <sp/Field.h>
#include <sp/GenericHandler.h>
#include <sp/MessageBase.h>
#include <memory>
enum MyMsgId {
MyMsgId_Msg1,
MyMsgId_Msg2,
};
class MyHandler; // forward declaration of the handler class.
using MyMessage = sp::Message<sp::option::MsgIdType<MyMsgId>, // add id() operation
sp::option::ReadOperations, // add read() operation
sp::option::WriteOperations, // add write() operation
sp::option::Handler<MyHandler>, // add dispatch() operation
sp::option::ValidCheckInterface, // add valid() operation
sp::option::LittleEndian // use little endian for serialisation
>;
using ActualMessage1Fields = std::tuple<sp::Field<std::uint16_t>, sp::Field<std::int8_t>>;
template <typename TMessageInterface>
class ActualMessage1 : public sp::MessageBase<TMessageInterface,
sp::option::StaticNumIdImpl<MyMsgId_Msg1>, // provide idImpl() if needed
sp::option::DispatchImpl<ActualMessage1<TMessageInterface>>, // provide dispatchImpl() if needed
sp::option::FieldsImpl<ActualMessage1Fields> // provide access to fields and
// readImpl(), writeImpl(),
// lengthImpl(), validImpl()
// functions if needed
> {};
using MyActualMessage1 = ActualMessage1<MyMessage>;
using AllMessages = std::tuple<MyActualMessage1>;
class MyHandler : public sp::GenericHandler<MyMessage, AllMessages> {
public:
void Handle(MyActualMessage1& msg) {
std::cout << "Got message 1 !\n";
}
};
int main() {
MyMessage::MsgIdType test;
auto yes = std::make_unique<ActualMessage1<MyMessage>>();
auto& fields = yes->GetFields();
std::get<0>(fields) = 69;
std::get<1>(fields) = 42;
MyHandler handlerTest;
yes->Dispatch(handlerTest);
sp::DataBuffer buffer;
yes->Write(buffer);
auto yesyes = std::make_unique<ActualMessage1<MyMessage>>();
yesyes->Read(buffer);
std::cout << "Field 1 : " << yesyes->GetField<0>() << std::endl;
std::cout << "Field 2 : " << (unsigned) yesyes->GetField<1>() << std::endl;
return 0;
}

View File

@@ -4,9 +4,6 @@
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include <sp/misc/Format.h>
#include <sp/misc/Log.h>
namespace sp { namespace sp {
DataBuffer::DataBuffer() : m_ReadOffset(0) {} DataBuffer::DataBuffer() : m_ReadOffset(0) {}
@@ -133,31 +130,19 @@ std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer) {
return os; return os;
} }
bool DataBuffer::ReadFile(const std::string& fileName) { void DataBuffer::ReadFile(const std::string& fileName) {
try { std::ifstream file(fileName, std::istream::binary);
std::ifstream file(fileName, std::istream::binary); std::ostringstream ss;
std::ostringstream ss; ss << file.rdbuf();
ss << file.rdbuf(); const std::string& s = ss.str();
const std::string& s = ss.str(); m_Buffer = DataBuffer::Data(s.begin(), s.end());
m_Buffer = DataBuffer::Data(s.begin(), s.end()); m_ReadOffset = 0;
m_ReadOffset = 0;
} catch (std::exception& e) {
utils::LOGE(utils::Format("[IO] Failed to read file %s ! reason : %s", fileName.c_str(), e.what()));
return false;
}
return m_Buffer.size() > 0;
} }
bool DataBuffer::WriteFile(const std::string& fileName) const { void DataBuffer::WriteFile(const std::string& fileName) const {
try { std::ofstream file(fileName, std::ostream::binary);
std::ofstream file(fileName, std::ostream::binary); file.write(reinterpret_cast<const char*>(m_Buffer.data()), static_cast<std::streamsize>(m_Buffer.size()));
file.write(reinterpret_cast<const char*>(m_Buffer.data()), static_cast<std::streamsize>(m_Buffer.size())); file.flush();
file.flush();
} catch (std::exception& e) {
utils::LOGE(utils::Format("[IO] Failed to read file %s ! reason : %s", fileName.c_str(), e.what()));
return false;
}
return true;
} }
} // namespace sp } // namespace sp

View File

@@ -5,8 +5,8 @@
namespace sp { namespace sp {
static constexpr int SEGMENT_BITS = 0x7F; static constexpr int SEGMENT_BITS = (1 << 7) - 1;
static constexpr int CONTINUE_BIT = 0x80; static constexpr int CONTINUE_BIT = 1 << 7;
std::size_t VarInt::GetSerializedLength() const { std::size_t VarInt::GetSerializedLength() const {
DataBuffer buffer; DataBuffer buffer;

View File

@@ -1,37 +0,0 @@
#include <sp/misc/Log.h>
#ifdef SP_ANDROID_LOGGING
#include <android/log.h>
#else
#include <iostream>
#endif
namespace sp {
namespace utils {
void LOG(const std::string& msg) {
#ifdef SP_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 SP_ANDROID_LOGGING
__android_log_print(ANDROID_LOG_ERROR, "TRACKERS", "%s", err.c_str());
#else
std::cerr << err << "\n";
#endif
}
} // namespace utils
} // namespace sp

View File

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

49
test/test_packets.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include <iostream>
#include <examples/PacketExample.h>
#include <memory>
class KeepAliveHandler : public sp::PacketHandler {
void Handle(const KeepAlivePacket& packet) {
std::cout << "KeepAlive handled !\n";
}
void Handle(const DisconnectPacket& packet) {
std::cout << "Disconnect handled !\n";
}
void Handle(const UpgradeTowerPacket& packet) {
std::cout << "UpgradeTower handled !\n";
}
};
int main() {
auto upgradeTower = std::make_unique<UpgradeTowerPacket>(std::make_tuple(666, 9));
sp::PacketMessage* msg = upgradeTower.get();
KeepAliveHandler handler;
msg->Dispatch(handler);
sp::DataBuffer buffer;
msg->Write(buffer);
std::uint8_t msgId;
buffer >> msgId;
auto upgradeTower2 = std::make_unique<UpgradeTowerPacket>();
upgradeTower2->Read(buffer);
std::cout << "Test : " << (unsigned) upgradeTower2->GetTowerId() << "\n";
sp::PacketFactory factory;
auto packet = factory.CreateMessage(msgId);
if (packet == nullptr) {
std::cout << "Bad ID !\n";
return 1;
}
std::cout << (unsigned)packet->GetId() << std::endl;
packet->Dispatch(handler);
return 0;
}

View File

@@ -2,15 +2,12 @@ add_rules("mode.debug", "mode.release")
set_languages("c++17") set_languages("c++17")
add_requires("enet6")
target("SimpleProtocolLib") target("SimpleProtocolLib")
add_includedirs("include", {public = true}) add_includedirs("include")
set_kind("binary") add_headerfiles("include/(sp/**.h)")
add_files("src/**.cpp") set_group("Library")
add_packages("enet6", {public = true}) add_files("src/sp/**.cpp")
set_kind("$(kind)")
-- Tests -- Tests
for _, file in ipairs(os.files("test/**.cpp")) do for _, file in ipairs(os.files("test/**.cpp")) do
@@ -19,79 +16,9 @@ for _, file in ipairs(os.files("test/**.cpp")) do
set_kind("binary") set_kind("binary")
add_files(file) add_files(file)
add_includedirs("include")
set_default(false)
add_deps("SimpleProtocolLib") add_deps("SimpleProtocolLib")
add_tests("compile_and_run") add_tests("compile_and_run")
end end
--
-- If you want to known more usage about xmake, please see https://xmake.io
--
-- ## FAQ
--
-- You can enter the project directory firstly before building project.
--
-- $ cd projectdir
--
-- 1. How to build project?
--
-- $ xmake
--
-- 2. How to configure project?
--
-- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release]
--
-- 3. Where is the build output directory?
--
-- The default output directory is `./build` and you can configure the output directory.
--
-- $ xmake f -o outputdir
-- $ xmake
--
-- 4. How to run and debug target after building project?
--
-- $ xmake run [targetname]
-- $ xmake run -d [targetname]
--
-- 5. How to install target to the system directory or other output directory?
--
-- $ xmake install
-- $ xmake install -o installdir
--
-- 6. Add some frequently-used compilation flags in xmake.lua
--
-- @code
-- -- add debug and release modes
-- add_rules("mode.debug", "mode.release")
--
-- -- add macro definition
-- add_defines("NDEBUG", "_GNU_SOURCE=1")
--
-- -- set warning all as error
-- set_warnings("all", "error")
--
-- -- set language: c99, c++11
-- set_languages("c99", "c++11")
--
-- -- set optimization: none, faster, fastest, smallest
-- set_optimize("fastest")
--
-- -- add include search directories
-- add_includedirs("/usr/include", "/usr/local/include")
--
-- -- add link libraries and search directories
-- add_links("tbox")
-- add_linkdirs("/usr/local/lib", "/usr/lib")
--
-- -- add system link libraries
-- add_syslinks("z", "pthread")
--
-- -- add compilation and link flags
-- add_cxflags("-stdnolib", "-fno-strict-aliasing")
-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true})
--
-- @endcode
--