commit cfeea10634c6e5bd9fcd0fac113f72f9d2b6ffed Author: Persson-dev Date: Tue Feb 4 19:11:03 2025 +0100 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e993027 --- /dev/null +++ b/.clang-format @@ -0,0 +1,37 @@ +Language: Cpp +BasedOnStyle: LLVM + +AlignAfterOpenBracket: DontAlign +BreakConstructorInitializers: AfterColon +ConstructorInitializerAllOnOneLineOrOnePerLine: true +PointerAlignment: Left +SortIncludes: true +SpacesBeforeTrailingComments: 2 +UseTab: Always +MaxEmptyLinesToKeep: 5 + +TabWidth: 4 +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +IndentWidth: 4 +IndentCaseLabels: true + +ColumnLimit: 135 +AlwaysBreakTemplateDeclarations: Yes + +AllowShortFunctionsOnASingleLine: Empty +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb11540 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Xmake cache +.xmake/ +build/ + +# MacOS Cache +.DS_Store + + +.vscode \ No newline at end of file diff --git a/include/sp/Types.h b/include/sp/Types.h new file mode 100644 index 0000000..ddb6c3f --- /dev/null +++ b/include/sp/Types.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace sp { + +using PeerID = std::uint16_t; + +} // namespace sp diff --git a/include/sp/common/DataBuffer.h b/include/sp/common/DataBuffer.h new file mode 100644 index 0000000..7c1f063 --- /dev/null +++ b/include/sp/common/DataBuffer.h @@ -0,0 +1,300 @@ +#pragma once + +/** + * \file DataBuffer.h + * \brief File containing the sp::DataBuffer class + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sp { + +/** + * \class DataBuffer + * \brief Class used to manipulate memory + */ +class DataBuffer { + private: + typedef std::vector Data; + Data m_Buffer; + std::size_t m_ReadOffset; + + public: + typedef Data::iterator iterator; + typedef Data::const_iterator const_iterator; + typedef Data::reference reference; + typedef Data::const_reference const_reference; + typedef Data::difference_type difference_type; + + DataBuffer(); + DataBuffer(const DataBuffer& other); + DataBuffer(const DataBuffer& other, difference_type offset); + DataBuffer(DataBuffer&& other); + DataBuffer(const std::string& str); + + DataBuffer& operator=(const DataBuffer& other); + DataBuffer& operator=(DataBuffer&& other); + + /** + * \brief Append data to the buffer + */ + template + void Append(const T& data) { + std::size_t size = sizeof(data); + std::size_t end_pos = m_Buffer.size(); + m_Buffer.resize(m_Buffer.size() + size); + std::memcpy(&m_Buffer[end_pos], &data, size); + } + + /** + * \brief Append data to the buffer + */ + template + DataBuffer& operator<<(const T& data) { + Append(data); + return *this; + } + + /** + * \brief Append a string to the buffer + * \warning Don't use it for binary data ! + * \param str The string to append + */ + DataBuffer& operator<<(const std::string& str); + + /** + * \brief Append data to the buffer from another const buffer + * \param data The buffer to append + */ + DataBuffer& operator<<(const DataBuffer& data); + + /** + * \brief Append a vector to the buffer by first writing the size + * \param data The vector to append + */ + template + DataBuffer& operator<<(const std::vector& data) { + *this << VarInt{data.size()}; + for (const auto& element : data) { + *this << element; + } + return *this; + } + + /** + * \brief Append an array to the buffer by first writing the size + * \param data The buffer to append + */ + template + DataBuffer& operator<<(const std::array& data) { + for (const auto& element : data) { + *this << element; + } + return *this; + } + + /** + * \brief Read some data from the buffer and assign to desired variable + */ + template + DataBuffer& operator>>(T& data) { + assert(m_ReadOffset + sizeof(T) <= GetSize()); + data = *(reinterpret_cast(&m_Buffer[m_ReadOffset])); + m_ReadOffset += sizeof(T); + return *this; + } + + /** + * \brief Read some data from the buffer and assign to the new buffer + * \param data The buffer to assign + */ + DataBuffer& operator>>(DataBuffer& data); + + /** + * \brief Read a string from the buffer + * \param str The string to assign in the new buffer + * \warning Don't use it for binary data ! + */ + DataBuffer& operator>>(std::string& str); + + /** + * \brief Read a vector (size + data) from the buffer + * \pre The vector is assumed to be empty + */ + template + DataBuffer& operator>>(std::vector& data) { + VarInt arraySize; + *this >> arraySize; + for (std::size_t i = 0; i < arraySize.GetValue(); i++) { + T newElement; + *this >> newElement; + data.push_back(newElement); + } + return *this; + } + + /** + * \brief Read an array from the buffer + */ + template + DataBuffer& operator>>(std::array& data) { + for (std::size_t i = 0; i < Size; i++) { + T newElement; + *this >> newElement; + data[i] = newElement; + } + return *this; + } + + /** + * \brief Write some data to the buffer + * \param buffer The buffer to write + * \param amount The amount of data to write + */ + void WriteSome(const char* buffer, std::size_t amount); + + /** + * \brief Write some data to the buffer + * \param buffer The buffer to write + * \param amount The amount of data to write + */ + void WriteSome(const std::uint8_t* buffer, std::size_t amount); + + /** + * \brief Read some data from the buffer + * \param buffer The buffer to Read + * \param amount The amount of data from the buffer + */ + void ReadSome(char* buffer, std::size_t amount); + + /** + * \brief Read some data from the buffer + * \param buffer The buffer to Read + * \param amount The amount of data from the buffer + */ + void ReadSome(std::uint8_t* buffer, std::size_t amount); + + /** + * \brief Read some data from the buffer + * \param buffer The buffer to Read + * \param amount The amount of data from the buffer + */ + void ReadSome(DataBuffer& buffer, std::size_t amount); + + /** + * \brief Resize the buffer + * \param size The new size of the buffer + */ + void Resize(std::size_t size) { + m_Buffer.resize(size); + } + + /** + * \brief Reserve some space in the buffer + * \param amount The amount of space to reserve + */ + void Reserve(std::size_t amount) { + m_Buffer.reserve(amount); + } + + + /** + * \brief Clear the buffer + */ + void Clear() { + m_Buffer.clear(); + m_ReadOffset = 0; + } + + /** + * \brief When the buffer has been read entirely + */ + bool IsFinished() const { + return m_ReadOffset >= m_Buffer.size(); + } + + /** + * \brief Get the buffer data + */ + std::uint8_t* data() { + return m_Buffer.data(); + } + + /** + * \brief Get the buffer data + */ + const std::uint8_t* data() const { + return m_Buffer.data(); + } + + /** + * \brief Get the read offset + */ + std::size_t GetReadOffset() const { + return m_ReadOffset; + } + + /** + * \brief Set the read offset + * \param pos The new read offset + */ + void SetReadOffset(std::size_t pos); + + /** + * \brief Get the size of the buffer + */ + std::size_t GetSize() const; + + /** + * \brief Get the remaining size of the buffer + */ + std::size_t GetRemaining() const; + + /** + * \brief Read a file into the buffer + * \param fileName The name of the file to read + */ + bool ReadFile(const std::string& fileName); + + /** + * \brief Write a file into the buffer + * \param fileName The name of the file to write to + */ + bool WriteFile(const std::string& fileName) const; + + /** + * \brief Allocate the buffer on the heap + * \warning Don't forget to free the data ! + */ + std::uint8_t* HeapAllocatedData() const { + std::uint8_t* newBuffer = new std::uint8_t[GetSize()]; + std::memcpy(newBuffer, data(), GetSize()); + return newBuffer; + } + + /** + * \brief Operator == to compare two DataBuffer + */ + bool operator==(const DataBuffer& other) const { + return m_Buffer == other.m_Buffer; + } + + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; +}; + +/** + * \brief Operator << to write a DataBuffer to an ostream + */ +std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer); + +} // namespace sp diff --git a/include/sp/common/NonCopyable.h b/include/sp/common/NonCopyable.h new file mode 100644 index 0000000..85b155e --- /dev/null +++ b/include/sp/common/NonCopyable.h @@ -0,0 +1,25 @@ +#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 diff --git a/include/sp/common/VarInt.h b/include/sp/common/VarInt.h new file mode 100644 index 0000000..8da0069 --- /dev/null +++ b/include/sp/common/VarInt.h @@ -0,0 +1,58 @@ +#pragma once + +/** + * \file VarInt.h + * \brief File containing the sp::VarInt class + */ + +#include +#include + +namespace sp { + +class DataBuffer; + +/** + * \class VarInt + * \brief Variable-length format such that smaller numbers use fewer bytes. + */ +class VarInt { + private: + std::uint64_t m_Value; + + public: + VarInt() : m_Value(0) {} + /** + * \brief Construct a variable integer from a value + * \param value The value of the variable integer + */ + VarInt(std::uint64_t value) : m_Value(value) {} + + /** + * \brief Get the value of the variable integer + */ + std::uint64_t GetValue() const { + return m_Value; + } + + /** + * \brief Get the length of the serialized variable integer + */ + std::size_t GetSerializedLength() const; + + /** + * \brief Serialize the variable integer + * \param out The buffer to write the serialized variable integer to + * \param var The variable integer to serialize + */ + friend DataBuffer& operator<<(DataBuffer& out, const VarInt& var); + + /** + * \brief Deserialize the variable integer + * \param in The buffer to read the serialized variable integer from + * \param var The variable integer to deserialize + */ + friend DataBuffer& operator>>(DataBuffer& in, VarInt& var); +}; + +} // namespace sp diff --git a/include/sp/misc/Format.h b/include/sp/misc/Format.h new file mode 100644 index 0000000..55ba136 --- /dev/null +++ b/include/sp/misc/Format.h @@ -0,0 +1,38 @@ +#pragma once + +/** + * \file Format.h + * \brief This file contains the definition of the `Format` function. + */ + +#include +#include +#include + +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 +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 buf(new char[size]); + snprintf(buf.get(), static_cast(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 diff --git a/include/sp/misc/Log.h b/include/sp/misc/Log.h new file mode 100644 index 0000000..4e83c7d --- /dev/null +++ b/include/sp/misc/Log.h @@ -0,0 +1,32 @@ +#pragma once + +/** + * \file Log.h + * \brief File defining log functions + */ + +#include + +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 diff --git a/include/sp/misc/Test.h b/include/sp/misc/Test.h new file mode 100644 index 0000000..da122c7 --- /dev/null +++ b/include/sp/misc/Test.h @@ -0,0 +1,70 @@ +#pragma once + +/** + * \file Test.h + * \brief File containing unit testing utilities + */ + +#include +#include + +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(__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 diff --git a/include/sp/protocol/Dispatcher.h b/include/sp/protocol/Dispatcher.h new file mode 100644 index 0000000..8ddfe9e --- /dev/null +++ b/include/sp/protocol/Dispatcher.h @@ -0,0 +1,61 @@ +#pragma once + +/** + * \file PacketDispatcher.h + * \brief File containing the sp::protocol::PacketDispatcher class + */ + +#include +#include + +#include + +namespace sp { +namespace protocol { + +/** + * \class Dispatcher + * \brief Class used to dispatch things + */ +template +class Dispatcher : private NonCopyable { + private: + std::map> m_Handlers; + + public: + /** + * \brief Constructor + */ + Dispatcher() {} + + /** + * \brief Dispatch a packet + * \param packet The packet to dispatch + */ + void Dispatch(const T& packet); + + /** + * \brief Register a packet handler + * \param type The packet type + * \param handler The packet handler + */ + void RegisterHandler(T_Enum type, T_Handler& handler); + + /** + * \brief Unregister a packet handler + * \param type The packet type + * \param handler The packet handler + */ + void UnregisterHandler(T_Enum type, T_Handler& handler); + + /** + * \brief Unregister a packet handler + * \param handler The packet handler + */ + void UnregisterHandler(T_Handler& handler); +}; + +} // namespace protocol +} // namespace sp + +#include "Dispatcher.inl" \ No newline at end of file diff --git a/include/sp/protocol/Dispatcher.inl b/include/sp/protocol/Dispatcher.inl new file mode 100644 index 0000000..22e4f5d --- /dev/null +++ b/include/sp/protocol/Dispatcher.inl @@ -0,0 +1,38 @@ +#pragma once + +namespace sp { +namespace protocol { + +template +void Dispatcher::Dispatch(const T& packet) { + T_Enum type = packet.GetType(); + for (auto* handler : m_Handlers[type]) + handler->Check(packet); +} + +template +void Dispatcher::RegisterHandler(T_Enum type, T_Handler& handler) { + auto found = std::find(m_Handlers[type].begin(), m_Handlers[type].end(), &handler); + if (found == m_Handlers[type].end()) + m_Handlers[type].push_back(&handler); +} + +template +void Dispatcher::UnregisterHandler(T_Enum type, T_Handler& handler) { + m_Handlers[type].erase(std::remove(m_Handlers[type].begin(), m_Handlers[type].end(), &handler), m_Handlers[type].end()); +} + +template +void Dispatcher::UnregisterHandler(T_Handler& handler) { + for (auto& pair : m_Handlers) { + if (pair.second.empty()) + continue; + + PacketType type = pair.first; + + m_Handlers[type].erase(std::remove(m_Handlers[type].begin(), m_Handlers[type].end(), &handler), m_Handlers[type].end()); + } +} + +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/command/CommandData.h b/include/sp/protocol/command/CommandData.h new file mode 100644 index 0000000..68e68c0 --- /dev/null +++ b/include/sp/protocol/command/CommandData.h @@ -0,0 +1,53 @@ +// #pragma once + +// #include +// #include +// #include + +// namespace sp { +// namespace protocol { + +// namespace cdata { + + +// struct PlaceTower { +// TowerType m_Type : 4; +// PlayerID m_Placer : 4; +// TowerCoords m_Position; +// }; + +// struct UpgradeTower { +// TowerID m_Tower : 12; +// std::uint8_t m_Upgrade : 4; +// }; + +// struct SpawnTroop { +// EntityType m_Type : 5; +// std::uint8_t m_Level : 3; +// EntityCoords m_Position; +// PlayerID m_Sender; +// }; + +// struct UseItem { +// ShopItem m_Item : 4; +// PlayerID m_User : 4; +// EntityCoords m_Position; +// }; + +// struct TeamChange { +// PlayerID m_Player : 7; +// Team m_NewTeam : 1; +// }; + +// struct PlayerJoin { +// PlayerID m_ID; +// std::string m_Name; +// }; + +// struct End {}; + +// } // namespace cdata + + +// } // namespace protocol +// } // namespace sp diff --git a/include/sp/protocol/command/CommandDeclare.h b/include/sp/protocol/command/CommandDeclare.h new file mode 100644 index 0000000..b08a7f1 --- /dev/null +++ b/include/sp/protocol/command/CommandDeclare.h @@ -0,0 +1,22 @@ +#pragma once + + +namespace sp { +namespace protocol { + +/** + * \def DeclareAllPacket + * \brief Avoids repetitive operations on commands + */ +#define DeclareAllCommand() \ + DeclareCommand(End) \ + DeclareCommand(PlaceTower) \ + DeclareCommand(PlayerJoin) \ + DeclareCommand(SpawnTroop) \ + DeclareCommand(TeamChange) \ + DeclareCommand(UpgradeTower) \ + DeclareCommand(UseItem) + + +} // namespace protocol +} // namespace sp \ No newline at end of file diff --git a/include/sp/protocol/command/CommandDispatcher.h b/include/sp/protocol/command/CommandDispatcher.h new file mode 100644 index 0000000..aae4d7b --- /dev/null +++ b/include/sp/protocol/command/CommandDispatcher.h @@ -0,0 +1,17 @@ +#pragma once + +/** + * \file CommandDispatcher.h + * \brief File containing the sp::protocol::CommandDispatcher class + */ + +#include +#include + +namespace sp { +namespace protocol { + +using CommandDispatcher = Dispatcher; + +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/command/CommandFactory.h b/include/sp/protocol/command/CommandFactory.h new file mode 100644 index 0000000..b4a91bd --- /dev/null +++ b/include/sp/protocol/command/CommandFactory.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace sp { +namespace protocol { +namespace CommandFactory { + +template ::value>::type> +std::shared_ptr CreateCommand() { + return std::make_shared(); +} + +const std::shared_ptr& CreateReadOnlyCommand(CommandType a_Type); + +} // namespace CommandFactory +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/command/CommandSerializer.h b/include/sp/protocol/command/CommandSerializer.h new file mode 100644 index 0000000..3b84a00 --- /dev/null +++ b/include/sp/protocol/command/CommandSerializer.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace sp { +namespace protocol { + +class Command; + +using CommandPtr = std::shared_ptr; + +namespace CommandSerializer { + +DataBuffer Serialize(const Command& a_Command); + +std::shared_ptr Deserialize(DataBuffer& a_Data); + +} // namespace CommandSerializer +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/command/CommandVisitor.h b/include/sp/protocol/command/CommandVisitor.h new file mode 100644 index 0000000..7908143 --- /dev/null +++ b/include/sp/protocol/command/CommandVisitor.h @@ -0,0 +1,39 @@ +#pragma once + +/** + * \file CommandVisitor.h + * \brief File containing the sp::protocol::CommandVisitor class + */ + +#include + +namespace sp { +namespace protocol { + +#define DeclareCommand(CommandName, ...) \ + /** This function is called when the packet processed by CommandVisitor::Check is a CommandName */ \ + virtual void Visit(const commands::CommandName&) {} + +/** + * \class CommandVisitor + * \brief This class uses double-dispatch in order to find the real type of a packet + */ +class CommandVisitor : private NonCopyable { + protected: + CommandVisitor() {} + virtual ~CommandVisitor() {} + + public: + /** + * \brief Calls the right CommandVisitor::Visit method corresponding to the real type of the packet + * \param packet the Command to visit + */ + void Check(const Command& packet); + + DeclareAllCommand() +}; + +#undef DeclareCommand + +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/command/Commands.h b/include/sp/protocol/command/Commands.h new file mode 100644 index 0000000..feaa058 --- /dev/null +++ b/include/sp/protocol/command/Commands.h @@ -0,0 +1,111 @@ +#pragma once + +/** + * \file Commands.h + * \brief File containing the definitions of the lockstep commands + */ + +#include +#include +#include +#include +#include + +namespace sp { +namespace protocol { + +class CommandVisitor; + +/** A Command id is 8 bits wide */ +using CommandID = std::uint8_t; + +#define DeclareCommand(CommandName, ...) /** CommandName */ CommandName, + +/** + * \enum CommandType + * \brief Map a Command to an id + */ +enum class CommandType : CommandID { + + DeclareAllCommand() + + /** The number of Commands */ + COMMAND_COUNT +}; + + +#undef DeclareCommand + + +class Command : private NonCopyable { + public: + /** + * \return The real type of the Command + */ + virtual CommandType GetType() const = 0; + + private: + /** Use a CommandVisitor to make double-dispatch possible */ + virtual void Accept(CommandVisitor& a_Visitor) const = 0; + + friend class CommandVisitor; +}; + + + + + +namespace commands { + +/** + * \class ConcreteCommand + * \brief A Command associated with an id and holding data + * \tparam PT The Command type + * \tparam Data The structure holding the data of the Command (in sp::protocol::data namespace) + */ +template +class ConcreteCommand : public Command { + public: + /** The type of the struct holding the data */ + using CommandDataType = Data; + + /** The structure holding the actual data */ + CommandDataType m_Data; + + /** Construct the Command with data of type CommandDataType */ + ConcreteCommand(const CommandDataType& a_Data = {}); + + constexpr CommandType GetType() const override { + return CT; + }; + + private: + void Accept(CommandVisitor& a_Visitor) const override; +}; + + + + + +// define SP_INSTANCIATE_COMMANDS +// before including this file +// if you want to instantiate templates +#ifdef SP_INSTANCIATE_COMMANDS +#define DeclareCommand(CommandName, ...) \ + using CommandName = ConcreteCommand; \ + template class ConcreteCommand; +#else +#define DeclareCommand(CommandName, ...) /** Defines the CommandName Command */ \ + using CommandName = ConcreteCommand; +#endif + +DeclareAllCommand() + +#undef DeclareCommand + +} // namespace commands + +using LockStep = std::vector>; + +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/packet/PacketData.h b/include/sp/protocol/packet/PacketData.h new file mode 100644 index 0000000..13f692b --- /dev/null +++ b/include/sp/protocol/packet/PacketData.h @@ -0,0 +1,76 @@ +// #pragma once + +// #include +// #include +// #include + +// // Make it dynamic ? +// #define LOCKSTEP_BUFFER_SIZE 10 + +// namespace sp { +// namespace protocol { + +// struct PlayerInfo { +// PlayerID m_PlayerId; +// std::string m_PlayerName; +// }; + +// struct MapData { + +// }; + +// namespace pdata { + +// /** Client attempts to login (very basic) */ +// struct PlayerLogin { +// std::string m_PlayerName; +// }; + +// /** Server indicates success */ +// struct LoggingSuccess { +// PlayerID m_PlayerId; +// }; + +// /** Player joins the lobby */ +// struct PlayerJoin { +// PlayerInfo m_Player; +// }; + +// /** Player leaves the lobby */ +// struct PlayerLeave { +// PlayerID m_PlayerId; +// }; + +// /** Keep alive */ +// struct KeepAlive { +// std::uint64_t m_KeepAliveId; +// }; + +// /** Can be used by both client and server */ +// struct Disconnect { +// std::string m_Reason; +// }; + +// /** Chat message */ +// struct ChatMessage { +// std::string m_Text; +// }; + +// // TODO: handle players joining in the first second + +// struct BeginGame { +// MapData m_Map; +// std::vector m_BlueTeam; +// std::vector m_RedTeam; +// // optional, used for players joining mid game +// std::vector m_FirstLocksteps; +// }; + +// struct LockSteps { +// std::uint16_t m_FirstFrameNumber; +// std::array m_LockSteps; +// }; + +// } // namespace pdata +// } // namespace protocol +// } // namespace sp diff --git a/include/sp/protocol/packet/PacketDeclare.h b/include/sp/protocol/packet/PacketDeclare.h new file mode 100644 index 0000000..44e4dce --- /dev/null +++ b/include/sp/protocol/packet/PacketDeclare.h @@ -0,0 +1,48 @@ +// #pragma once + +// /** +// * \file PacketDeclare.h +// * \brief Holds the definitions of the packets (but not their content) +// */ + +// namespace sp { +// namespace protocol { + +// /** +// * \enum PacketSender +// * \brief Indicate who should send a packet +// */ +// enum class PacketSenderType { +// /** Sent by clients and server */ +// Both = 1, +// /** Sent by clients to the server */ +// Client, +// /** Sent by server to the clients */ +// Server, +// }; + +// enum class PacketSendType { +// Reliable = 1, +// Unreliable, +// UnreliableOrdered, +// }; + + +// /** +// * \def DeclareAllPacket +// * \brief Avoids repetitive operations on packets +// */ +// #define DeclareAllPacket() \ +// DeclarePacket(ChatMessage, Reliable, Both) \ +// DeclarePacket(BeginGame, Reliable, Server) \ +// DeclarePacket(Disconnect, Reliable, Both) \ +// DeclarePacket(KeepAlive, Reliable, Both) \ +// DeclarePacket(LockSteps, Unreliable, Both) \ +// DeclarePacket(LoggingSuccess, Reliable, Server) \ +// DeclarePacket(PlayerJoin, Reliable, Server) \ +// DeclarePacket(PlayerLeave, Reliable, Server) \ +// DeclarePacket(PlayerLogin, Reliable, Client) \ + + +// } // namespace protocol +// } // namespace sp \ No newline at end of file diff --git a/include/sp/protocol/packet/PacketDispatcher.h b/include/sp/protocol/packet/PacketDispatcher.h new file mode 100644 index 0000000..de04f58 --- /dev/null +++ b/include/sp/protocol/packet/PacketDispatcher.h @@ -0,0 +1,17 @@ +#pragma once + +/** + * \file PacketDispatcher.h + * \brief File containing the sp::protocol::PacketDispatcher class + */ + +#include +#include + +namespace sp { +namespace protocol { + +using PacketDispatcher = Dispatcher; + +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/packet/PacketFactory.h b/include/sp/protocol/packet/PacketFactory.h new file mode 100644 index 0000000..fc085e7 --- /dev/null +++ b/include/sp/protocol/packet/PacketFactory.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace sp { +namespace protocol { +namespace PacketFactory { + +template ::value>::type> +std::unique_ptr CreatePacket() { + return std::make_unique(); +} + +const std::unique_ptr& CreateReadOnlyPacket(PacketType a_Type); + +} // namespace PacketFactory +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/packet/PacketSerializer.h b/include/sp/protocol/packet/PacketSerializer.h new file mode 100644 index 0000000..972299a --- /dev/null +++ b/include/sp/protocol/packet/PacketSerializer.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace sp { +namespace protocol { + +class Packet; + +using PacketPtr = std::unique_ptr; + +namespace PacketSerializer { + +DataBuffer Serialize(const Packet& a_Packet); + +std::unique_ptr Deserialize(DataBuffer& a_Data); + +} // namespace PacketSerializer +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/packet/PacketVisitor.h b/include/sp/protocol/packet/PacketVisitor.h new file mode 100644 index 0000000..1347153 --- /dev/null +++ b/include/sp/protocol/packet/PacketVisitor.h @@ -0,0 +1,39 @@ +#pragma once + +/** + * \file PacketVisitor.h + * \brief File containing the sp::protocol::PacketVisitor class + */ + +#include + +namespace sp { +namespace protocol { + +#define DeclarePacket(PacketName, ...) \ + /** This function is called when the packet processed by PacketVisitor::Check is a PacketName */ \ + virtual void Visit(const packets::PacketName&) {} + +/** + * \class PacketVisitor + * \brief This class uses double-dispatch in order to find the real type of a packet + */ +class PacketVisitor : private NonCopyable { + protected: + PacketVisitor() {} + virtual ~PacketVisitor() {} + + public: + /** + * \brief Calls the right PacketVisitor::Visit method corresponding to the real type of the packet + * \param packet the Packet to visit + */ + void Check(const Packet& packet); + + DeclareAllPacket() +}; + +#undef DeclarePacket + +} // namespace protocol +} // namespace sp diff --git a/include/sp/protocol/packet/Packets.h b/include/sp/protocol/packet/Packets.h new file mode 100644 index 0000000..524f9a0 --- /dev/null +++ b/include/sp/protocol/packet/Packets.h @@ -0,0 +1,115 @@ +#pragma once + +/** + * \file Packets.h + * \brief File containing the definitions of the packets + */ + +#include +#include +#include + +#include + +namespace sp { +namespace protocol { + +class PacketVisitor; + +/** A Packet id is 8 bits wide */ +using PacketID = std::uint8_t; +using PeerID = std::uint16_t; + +#define DeclarePacket(PacketName, ...) /** PacketName */ PacketName, + +/** + * \enum PacketType + * \brief Map a Packet to an id + */ +enum class PacketType : PacketID { + + DeclareAllPacket() + + /** The number of packets */ + PACKET_COUNT +}; + + +#undef DeclarePacket + + +class Packet : private NonCopyable { + public: + /** + * \return The real type of the packet + */ + virtual PacketType GetType() const = 0; + + /** + * \brief The network peer who sent the packet + */ + PeerID m_Sender; + + private: + /** Use a PacketVisitor to make double-dispatch possible */ + virtual void Accept(PacketVisitor& a_Visitor) const = 0; + + friend class PacketVisitor; +}; + + + + + +namespace packets { + +/** + * \class ConcretePacket + * \brief A Packet associated with an id and holding data + * \tparam PT The packet type + * \tparam Data The structure holding the data of the packet (in sp::protocol::data namespace) + */ +template +class ConcretePacket : public Packet { + public: + /** The type of the struct holding the data */ + using PacketDataType = Data; + + /** The structure holding the actual data */ + PacketDataType m_Data; + + /** Construct the packet with data of type PacketDataType */ + ConcretePacket(const PacketDataType& a_Data = {}); + + constexpr PacketType GetType() const override { + return PT; + }; + + private: + void Accept(PacketVisitor& a_Visitor) const override; +}; + + + + + +// define SP_INSTANCIATE_PACKETS +// before including this file +// if you want to instantiate templates +#ifdef SP_INSTANCIATE_PACKETS +#define DeclarePacket(PacketName, ...) \ + using PacketName = ConcretePacket; \ + template class ConcretePacket; +#else +#define DeclarePacket(PacketName, ...) /** Defines the PacketName packet */ \ + using PacketName = ConcretePacket; +#endif + +// DeclareAllPacket() + +#undef DeclarePacket + +} // namespace packets + +} // namespace protocol +} // namespace sp diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..c1e10b4 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,21 @@ +#include +// #include +#include +#include +#include +#include +#include + +class Test : public sp::protocol::CommandVisitor {}; + +int main(int argc, char** argv) { + // Test visitor; + // sp::protocol::packets::ChatMessage chat{{"coucou"}}; + // visitor.Check(chat); + + + td::protocol::commands::UpgradeTower com{{1, 2}}; + std::cout << (unsigned)com.GetType() << std::endl; + td::protocol::CommandDispatcher disptacher; + return 0; +} diff --git a/src/sp/common/DataBuffer.cpp b/src/sp/common/DataBuffer.cpp new file mode 100644 index 0000000..42d424f --- /dev/null +++ b/src/sp/common/DataBuffer.cpp @@ -0,0 +1,163 @@ +#include + +#include +#include +#include + +#include +#include + +namespace sp { + +DataBuffer::DataBuffer() : m_ReadOffset(0) {} + +DataBuffer::DataBuffer(const DataBuffer& other) : m_Buffer(other.m_Buffer), m_ReadOffset(other.m_ReadOffset) {} + +DataBuffer::DataBuffer(DataBuffer&& other) : m_Buffer(std::move(other.m_Buffer)), m_ReadOffset(std::move(other.m_ReadOffset)) {} + +DataBuffer::DataBuffer(const std::string& str) : m_Buffer(str.begin(), str.end()), m_ReadOffset(0) {} + +DataBuffer::DataBuffer(const DataBuffer& other, Data::difference_type offset) : m_ReadOffset(0) { + m_Buffer.reserve(other.GetSize() - static_cast(offset)); + std::copy(other.m_Buffer.begin() + offset, other.m_Buffer.end(), std::back_inserter(m_Buffer)); +} + +DataBuffer& DataBuffer::operator<<(const std::string& str) { + std::size_t strlen = str.length() + 1; // including null character + Resize(GetSize() + strlen); + std::memcpy(m_Buffer.data() + GetSize() - strlen, str.data(), strlen); + return *this; +} + +DataBuffer& DataBuffer::operator<<(const DataBuffer& data) { + m_Buffer.insert(m_Buffer.end(), data.begin(), data.end()); + return *this; +} + +DataBuffer& DataBuffer::operator>>(std::string& str) { + std::size_t stringSize = strlen(reinterpret_cast(m_Buffer.data()) + m_ReadOffset) + 1; // including null character + str.resize(stringSize); + std::copy(m_Buffer.begin() + static_cast(m_ReadOffset), + m_Buffer.begin() + static_cast(m_ReadOffset + stringSize), str.begin()); + m_ReadOffset += stringSize; + str.resize(stringSize - 1); + return *this; +} + +DataBuffer& DataBuffer::operator>>(DataBuffer& data) { + data.Resize(GetSize() - m_ReadOffset); + std::copy(m_Buffer.begin() + static_cast(m_ReadOffset), m_Buffer.end(), data.begin()); + m_ReadOffset = m_Buffer.size(); + return *this; +} + +void DataBuffer::WriteSome(const char* buffer, std::size_t amount) { + std::size_t end_pos = m_Buffer.size(); + m_Buffer.resize(m_Buffer.size() + amount); + std::memcpy(m_Buffer.data() + end_pos, buffer, amount); +} + +void DataBuffer::WriteSome(const std::uint8_t* buffer, std::size_t amount) { + std::size_t end_pos = m_Buffer.size(); + m_Buffer.resize(m_Buffer.size() + amount); + std::memcpy(m_Buffer.data() + end_pos, buffer, amount); +} + +void DataBuffer::ReadSome(char* buffer, std::size_t amount) { + assert(m_ReadOffset + amount <= GetSize()); + std::copy_n(m_Buffer.begin() + static_cast(m_ReadOffset), amount, buffer); + m_ReadOffset += amount; +} + + +void DataBuffer::ReadSome(std::uint8_t* buffer, std::size_t amount) { + assert(m_ReadOffset + amount <= GetSize()); + std::copy_n(m_Buffer.begin() + static_cast(m_ReadOffset), amount, buffer); + m_ReadOffset += amount; +} + + +void DataBuffer::ReadSome(DataBuffer& buffer, std::size_t amount) { + assert(m_ReadOffset + amount <= GetSize()); + buffer.Resize(amount); + std::copy_n(m_Buffer.begin() + static_cast(m_ReadOffset), amount, buffer.begin()); + m_ReadOffset += amount; +} + + +DataBuffer& DataBuffer::operator=(const DataBuffer& other) { + m_Buffer = other.m_Buffer; + m_ReadOffset = other.m_ReadOffset; + return *this; +} + +DataBuffer& DataBuffer::operator=(DataBuffer&& other) { + m_Buffer = std::move(other.m_Buffer); + m_ReadOffset = std::move(other.m_ReadOffset); + return *this; +} + +void DataBuffer::SetReadOffset(std::size_t pos) { + assert(pos <= GetSize()); + m_ReadOffset = pos; +} + +std::size_t DataBuffer::GetSize() const { + return m_Buffer.size(); +} + +std::size_t DataBuffer::GetRemaining() const { + return m_Buffer.size() - m_ReadOffset; +} + +DataBuffer::iterator DataBuffer::begin() { + return m_Buffer.begin(); +} + +DataBuffer::iterator DataBuffer::end() { + return m_Buffer.end(); +} + +DataBuffer::const_iterator DataBuffer::begin() const { + return m_Buffer.begin(); +} + +DataBuffer::const_iterator DataBuffer::end() const { + return m_Buffer.end(); +} + +std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer) { + for (unsigned char u : buffer) + os << std::hex << std::setfill('0') << std::setw(2) << static_cast(u) << " "; + os << std::dec; + return os; +} + +bool DataBuffer::ReadFile(const std::string& fileName) { + try { + std::ifstream file(fileName, std::istream::binary); + std::ostringstream ss; + ss << file.rdbuf(); + const std::string& s = ss.str(); + m_Buffer = DataBuffer::Data(s.begin(), s.end()); + 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 { + try { + std::ofstream file(fileName, std::ostream::binary); + file.write(reinterpret_cast(m_Buffer.data()), static_cast(m_Buffer.size())); + 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 diff --git a/src/sp/common/VarInt.cpp b/src/sp/common/VarInt.cpp new file mode 100644 index 0000000..db62d06 --- /dev/null +++ b/src/sp/common/VarInt.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include + +namespace sp { + +static constexpr int SEGMENT_BITS = 0x7F; +static constexpr int CONTINUE_BIT = 0x80; + +std::size_t VarInt::GetSerializedLength() const { + DataBuffer buffer; + buffer << *this; + return buffer.GetSize(); +} + +DataBuffer& operator<<(DataBuffer& out, const VarInt& var) { + std::uint64_t value = var.m_Value; + while (true) { + if ((value & ~static_cast(SEGMENT_BITS)) == 0) { + out << static_cast(value); + return out; + } + + out << static_cast((value & SEGMENT_BITS) | CONTINUE_BIT); + + value >>= 7; + } +} + +DataBuffer& operator>>(DataBuffer& in, VarInt& var) { + var.m_Value = 0; + unsigned int position = 0; + std::uint8_t currentByte; + + while (true) { + in.ReadSome(¤tByte, 1); + var.m_Value |= static_cast(currentByte & SEGMENT_BITS) << position; + + if ((currentByte & CONTINUE_BIT) == 0) + break; + + position += 7; + + if (position >= 8 * sizeof(var.m_Value)) + throw std::runtime_error("VarInt is too big"); + } + + return in; +} + +} // namespace sp diff --git a/src/sp/misc/Log.cpp b/src/sp/misc/Log.cpp new file mode 100644 index 0000000..829f46d --- /dev/null +++ b/src/sp/misc/Log.cpp @@ -0,0 +1,37 @@ +#include + + + +#ifdef SP_ANDROID_LOGGING +#include +#else +#include +#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 diff --git a/src/sp/misc/Test.cpp b/src/sp/misc/Test.cpp new file mode 100644 index 0000000..28cb8ff --- /dev/null +++ b/src/sp/misc/Test.cpp @@ -0,0 +1,14 @@ +#include +#include +#include + +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 diff --git a/src/sp/protocol/command/CommandFactory.cpp b/src/sp/protocol/command/CommandFactory.cpp new file mode 100644 index 0000000..ad621d8 --- /dev/null +++ b/src/sp/protocol/command/CommandFactory.cpp @@ -0,0 +1,24 @@ +#include + +#include +#include +#include + +namespace sp { +namespace protocol { +namespace CommandFactory { + +using CommandCreator = std::function()>; + +#define DeclareCommand(CommandName, ...) std::make_shared(), + +static std::array, static_cast(CommandType::COMMAND_COUNT)> Commands = {DeclareAllCommand()}; + +const std::shared_ptr& CreateReadOnlyCommand(CommandType a_Type) { + assert(a_Type < CommandType::COMMAND_COUNT); + return Commands[static_cast(a_Type)]; +} + +} // namespace CommandFactory +} // namespace protocol +} // namespace sp diff --git a/src/sp/protocol/command/CommandSerializer.cpp b/src/sp/protocol/command/CommandSerializer.cpp new file mode 100644 index 0000000..fb3ef49 --- /dev/null +++ b/src/sp/protocol/command/CommandSerializer.cpp @@ -0,0 +1,229 @@ +#include + +#include +#include + +#include +#include + +namespace sp { +namespace protocol { +namespace CommandSerializer { + +#define DeclareCommand(CommandName, ...) \ + void Visit(const commands::CommandName& a_Command) override { \ + const auto& commandData = a_Command.m_Data; \ + SerializeCommandData(commandData); \ + } \ + \ + void SerializeCommandData(const commands::CommandName::CommandDataType& a_Command); + + + + +class Serializer : public CommandVisitor { + private: + DataBuffer& m_Buffer; + + public: + Serializer(DataBuffer& a_Buffer) : m_Buffer(a_Buffer) {} + + void Serialize(const Command& a_Command) { + m_Buffer << static_cast(a_Command.GetType()); + Check(a_Command); + } + + DeclareAllCommand() +}; + +#undef DeclareCommand + + + + + +#define DeclareCommand(CommandName, ...) \ + void Visit(const commands::CommandName& a_Command) override { \ + auto commandPtr = CommandFactory::CreateCommand(); \ + auto& commandData = commandPtr->m_Data; \ + \ + DeserializeCommandData(commandData); \ + \ + m_Command = std::move(commandPtr); \ + } \ + \ + void DeserializeCommandData(commands::CommandName::CommandDataType& a_Command); + + + +class Deserializer : public CommandVisitor { + private: + DataBuffer& m_Buffer; + CommandPtr m_Command; + + public: + Deserializer(DataBuffer& a_Buffer) : m_Buffer(a_Buffer) {} + + bool Deserialize(const CommandPtr& a_Command) { + try { + Check(*a_Command.get()); + } catch (std::exception& e) { + utils::LOGE(utils::Format("[CommandSerializer::Deserializer] %s", e.what())); + return false; + } + return true; + } + + CommandPtr& GetCommand() { + return m_Command; + } + + DeclareAllCommand() +}; + + + + + +DataBuffer Serialize(const Command& a_Command) { + DataBuffer buffer; + + Serializer serializer(buffer); + serializer.Serialize(a_Command); + + return buffer; +} + +std::shared_ptr Deserialize(DataBuffer& a_Data) { + CommandID commandId; + a_Data >> commandId; + + if (commandId >= static_cast(CommandType::COMMAND_COUNT)) + return nullptr; + + CommandType commandType = CommandType(commandId); + + // for double-dispatch + const CommandPtr& emptyCommand = CommandFactory::CreateReadOnlyCommand(commandType); + + Deserializer deserializer(a_Data); + + if (deserializer.Deserialize(emptyCommand)) { + CommandPtr command = std::move(deserializer.GetCommand()); + return command; + } + + return nullptr; +} + + + + + +//--------------------------------------------- +// Command serializer implementation +//---------------------------------------------- + + + + + +void Serializer::SerializeCommandData(const cdata::End& a_Command) {} + +void Deserializer::DeserializeCommandData(cdata::End& a_Command) {} + + + + + +void Serializer::SerializeCommandData(const cdata::PlaceTower& a_Command) { + m_Buffer << static_cast((static_cast(a_Command.m_Type) << 4 | a_Command.m_Placer & 0xF)) + << a_Command.m_Position; +} + +void Deserializer::DeserializeCommandData(cdata::PlaceTower& a_Command) { + std::uint8_t union1; + m_Buffer >> union1 >> a_Command.m_Position; + a_Command.m_Type = sp::TowerType(union1 >> 4); + a_Command.m_Placer = union1 & 0xF; +} + + + + + +void Serializer::SerializeCommandData(const cdata::PlayerJoin& a_Command) { + m_Buffer << a_Command.m_ID << a_Command.m_Name; +} + +void Deserializer::DeserializeCommandData(cdata::PlayerJoin& a_Command) { + m_Buffer >> a_Command.m_ID >> a_Command.m_Name; +} + + + + +void Serializer::SerializeCommandData(const cdata::SpawnTroop& a_Command) { + m_Buffer << static_cast(static_cast(a_Command.m_Type) << 3 | a_Command.m_Level & 0x7) + << a_Command.m_Position << a_Command.m_Sender; +} + +void Deserializer::DeserializeCommandData(cdata::SpawnTroop& a_Command) { + std::uint8_t union1; + m_Buffer >> union1 >> a_Command.m_Position >> a_Command.m_Sender; + a_Command.m_Type = sp::EntityType(union1 >> 3); + a_Command.m_Level = union1 & 0x7; +} + + + + + +void Serializer::SerializeCommandData(const cdata::TeamChange& a_Command) { + m_Buffer << static_cast(a_Command.m_Player << 1 | static_cast(a_Command.m_NewTeam)); +} + +void Deserializer::DeserializeCommandData(cdata::TeamChange& a_Command) { + std::uint8_t union1; + m_Buffer >> union1; + a_Command.m_Player = union1 >> 1; + a_Command.m_NewTeam = sp::Team(union1 & 1); +} + + + + + +void Serializer::SerializeCommandData(const cdata::UpgradeTower& a_Command) { + m_Buffer << static_cast(a_Command.m_Tower << 4 | a_Command.m_Upgrade & 0xF); +} + +void Deserializer::DeserializeCommandData(cdata::UpgradeTower& a_Command) { + std::uint16_t union1; + m_Buffer >> union1; + a_Command.m_Tower = union1 >> 4; + a_Command.m_Upgrade = union1 & 0xF; +} + + + + + +void Serializer::SerializeCommandData(const cdata::UseItem& a_Command) { + m_Buffer << static_cast(static_cast(a_Command.m_Item) << 4 | a_Command.m_User & 0xF) + << a_Command.m_Position; +} + +void Deserializer::DeserializeCommandData(cdata::UseItem& a_Command) { + std::uint8_t union1; + m_Buffer >> union1 >> a_Command.m_Position; + a_Command.m_Item = sp::ShopItem(union1 >> 4); + a_Command.m_User = union1 & 0xF; +} + + + + +} // namespace CommandSerializer +} // namespace protocol +} // namespace sp diff --git a/src/sp/protocol/command/CommandVisitor.cpp b/src/sp/protocol/command/CommandVisitor.cpp new file mode 100644 index 0000000..d4e6bda --- /dev/null +++ b/src/sp/protocol/command/CommandVisitor.cpp @@ -0,0 +1,11 @@ +#include + +namespace sp { +namespace protocol { + +void CommandVisitor::Check(const Command& a_Command) { + a_Command.Accept(*this); +} + +} // namespace protocol +} // namespace sp diff --git a/src/sp/protocol/command/Commands.cpp b/src/sp/protocol/command/Commands.cpp new file mode 100644 index 0000000..48d936d --- /dev/null +++ b/src/sp/protocol/command/Commands.cpp @@ -0,0 +1,18 @@ +#define SP_INSTANCIATE_COMMANDS +#include + +#include + +namespace sp { +namespace protocol { + +template +commands::ConcreteCommand::ConcreteCommand(const CommandDataType& a_Data) : m_Data(a_Data) {} + +template +void commands::ConcreteCommand::Accept(CommandVisitor& a_Visitor) const { + a_Visitor.Visit(*this); +} + +} // namespace protocol +} // namespace sp \ No newline at end of file diff --git a/src/sp/protocol/packet/PacketFactory.cpp b/src/sp/protocol/packet/PacketFactory.cpp new file mode 100644 index 0000000..f857a6d --- /dev/null +++ b/src/sp/protocol/packet/PacketFactory.cpp @@ -0,0 +1,26 @@ +#include + +#include +#include +#include + +namespace sp { +namespace protocol { +namespace PacketFactory { + +using PacketCreator = std::function()>; + +#define DeclarePacket(PacketName, ...) std::make_unique(), + +static std::array, static_cast(PacketType::PACKET_COUNT)> Packets = { + DeclareAllPacket() +}; + +const std::unique_ptr& CreateReadOnlyPacket(PacketType a_Type) { + assert(a_Type < PacketType::PACKET_COUNT); + return Packets[static_cast(a_Type)]; +} + +} // namespace PacketFactory +} // namespace protocol +} // namespace sp diff --git a/src/sp/protocol/packet/PacketSerializer.cpp b/src/sp/protocol/packet/PacketSerializer.cpp new file mode 100644 index 0000000..42753d8 --- /dev/null +++ b/src/sp/protocol/packet/PacketSerializer.cpp @@ -0,0 +1,262 @@ +#include + +#include +#include +#include + +#include +#include + +namespace sp { +namespace protocol { +namespace PacketSerializer { + +#define DeclarePacket(PacketName, ...) \ + void Visit(const packets::PacketName& a_Packet) override { \ + const auto& packetData = a_Packet.m_Data; \ + SerializePacketData(packetData); \ + } \ + \ + void SerializePacketData(const packets::PacketName::PacketDataType& a_Packet); + + + + +class Serializer : public PacketVisitor { + private: + DataBuffer& m_Buffer; + + public: + Serializer(DataBuffer& a_Buffer) : m_Buffer(a_Buffer) {} + + void Serialize(const Packet& a_Packet) { + m_Buffer << static_cast(a_Packet.GetType()); + Check(a_Packet); + } + + DeclareAllPacket() +}; + +#undef DeclarePacket + + + + + +#define DeclarePacket(PacketName, ...) \ + void Visit(const packets::PacketName& a_Packet) override { \ + auto packetPtr = PacketFactory::CreatePacket(); \ + auto& packetData = packetPtr->m_Data; \ + \ + DeserializePacketData(packetData); \ + \ + m_Packet = std::move(packetPtr); \ + } \ + \ + void DeserializePacketData(packets::PacketName::PacketDataType& a_Packet); + + + +class Deserializer : public PacketVisitor { + private: + DataBuffer& m_Buffer; + PacketPtr m_Packet; + + public: + Deserializer(DataBuffer& a_Buffer) : m_Buffer(a_Buffer) {} + + bool Deserialize(const PacketPtr& a_Packet) { + try { + Check(*a_Packet.get()); + } catch (std::exception& e) { + utils::LOGE(utils::Format("[PacketSerializer::Deserializer] %s", e.what())); + return false; + } + return true; + } + + PacketPtr& GetPacket() { + return m_Packet; + } + + DeclareAllPacket() +}; + + + + + +DataBuffer Serialize(const Packet& a_Packet) { + DataBuffer buffer; + + Serializer serializer(buffer); + serializer.Serialize(a_Packet); + + return buffer; +} + +std::unique_ptr Deserialize(DataBuffer& a_Data) { + PacketID packetId; + a_Data >> packetId; + + if (packetId >= static_cast(PacketType::PACKET_COUNT)) + return nullptr; + + PacketType packetType = PacketType(packetId); + + // for double-dispatch + const PacketPtr& emptyPacket = PacketFactory::CreateReadOnlyPacket(packetType); + + Deserializer deserializer(a_Data); + + if (deserializer.Deserialize(emptyPacket)) { + PacketPtr packet = std::move(deserializer.GetPacket()); + return packet; + } + + return nullptr; +} + + + + + +//--------------------------------------------- +// Packet serializer implementation +//---------------------------------------------- + + + + + +void Serializer::SerializePacketData(const pdata::PlayerLogin& a_Packet) { + m_Buffer << a_Packet.m_PlayerName; +} + +void Deserializer::DeserializePacketData(pdata::PlayerLogin& a_Packet) { + m_Buffer >> a_Packet.m_PlayerName; +} + + + + + +void Serializer::SerializePacketData(const pdata::LoggingSuccess& a_Packet) { + m_Buffer << a_Packet.m_PlayerId; +} + +void Deserializer::DeserializePacketData(pdata::LoggingSuccess& a_Packet) { + m_Buffer >> a_Packet.m_PlayerId; +} + + + + + +void Serializer::SerializePacketData(const pdata::PlayerJoin& a_Packet) { + m_Buffer << a_Packet.m_Player; +} + +void Deserializer::DeserializePacketData(pdata::PlayerJoin& a_Packet) { + m_Buffer >> a_Packet.m_Player; +} + + + + +void Serializer::SerializePacketData(const pdata::PlayerLeave& a_Packet) { + m_Buffer << a_Packet.m_PlayerId; +} + +void Deserializer::DeserializePacketData(pdata::PlayerLeave& a_Packet) { + m_Buffer >> a_Packet.m_PlayerId; +} + + + + + +void Serializer::SerializePacketData(const pdata::KeepAlive& a_Packet) { + m_Buffer << a_Packet.m_KeepAliveId; +} + +void Deserializer::DeserializePacketData(pdata::KeepAlive& a_Packet) { + m_Buffer >> a_Packet.m_KeepAliveId; +} + + + + + +void Serializer::SerializePacketData(const pdata::Disconnect& a_Packet) { + m_Buffer << a_Packet.m_Reason; +} + +void Deserializer::DeserializePacketData(pdata::Disconnect& a_Packet) { + m_Buffer >> a_Packet.m_Reason; +} + + + + + +void Serializer::SerializePacketData(const pdata::ChatMessage& a_Packet) { + m_Buffer << a_Packet.m_Text; +} + +void Deserializer::DeserializePacketData(pdata::ChatMessage& a_Packet) { + m_Buffer >> a_Packet.m_Text; +} + + + + + +void Serializer::SerializePacketData(const pdata::LockSteps& a_Packet) { + m_Buffer << a_Packet.m_FirstFrameNumber; + for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) { + auto& lockStep = a_Packet.m_LockSteps[i]; + m_Buffer << VarInt{lockStep.size()}; + + for (int j = 0; j < lockStep.size(); i++) { + auto command = lockStep[j]; + m_Buffer << CommandSerializer::Serialize(*command); + } + } +} + +void Deserializer::DeserializePacketData(pdata::LockSteps& a_Packet) { + m_Buffer >> a_Packet.m_FirstFrameNumber; + for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) { + VarInt lockStepSize; + m_Buffer >> lockStepSize; + + for (std::size_t j = 0; j < lockStepSize.GetValue(); i++) { + auto command = CommandSerializer::Deserialize(m_Buffer); + if (command) { + a_Packet.m_LockSteps[i].push_back(command); + } else { + throw std::runtime_error("Failed to deserialise command"); + } + } + } +} + + + + + +void Serializer::SerializePacketData(const pdata::BeginGame& a_Packet) { + m_Buffer << a_Packet.m_Map << a_Packet.m_BlueTeam << a_Packet.m_RedTeam << a_Packet.m_Map; +} + +void Deserializer::DeserializePacketData(pdata::BeginGame& a_Packet) { + m_Buffer >> a_Packet.m_Map >> a_Packet.m_BlueTeam >> a_Packet.m_RedTeam >> a_Packet.m_Map; +} + + + + +} // namespace PacketSerializer +} // namespace protocol +} // namespace sp diff --git a/src/sp/protocol/packet/PacketVisitor.cpp b/src/sp/protocol/packet/PacketVisitor.cpp new file mode 100644 index 0000000..ca7e918 --- /dev/null +++ b/src/sp/protocol/packet/PacketVisitor.cpp @@ -0,0 +1,11 @@ +#include + +namespace sp { +namespace protocol { + +void PacketVisitor::Check(const Packet& a_Packet) { + a_Packet.Accept(*this); +} + +} // namespace protocol +} // namespace sp diff --git a/src/sp/protocol/packet/Packets.cpp b/src/sp/protocol/packet/Packets.cpp new file mode 100644 index 0000000..4449807 --- /dev/null +++ b/src/sp/protocol/packet/Packets.cpp @@ -0,0 +1,23 @@ +#define SP_INSTANCIATE_PACKETS +#include + +#include + +namespace sp { +namespace protocol { + +template +packets::ConcretePacket::ConcretePacket(const PacketDataType& a_Data) : m_Data(a_Data) {} + +template +void packets::ConcretePacket::Accept(PacketVisitor& a_Visitor) const { + a_Visitor.Visit(*this); +} + +#define DeclarePacket(PacketName, packetSendType, packetSenderType) \ + static_assert(static_cast(PacketSendType::packetSendType) && static_cast(PacketSenderType::packetSenderType)); + +DeclareAllPacket() + +} // namespace protocol +} // namespace sp diff --git a/test/sp/protocol/command/CommandFactory_test.cpp b/test/sp/protocol/command/CommandFactory_test.cpp new file mode 100644 index 0000000..d1b12c8 --- /dev/null +++ b/test/sp/protocol/command/CommandFactory_test.cpp @@ -0,0 +1,17 @@ +// #include + +// #include + +// static int Test() { +// for (std::size_t i = 0; i < static_cast(td::protocol::CommandType::COMMAND_COUNT); i++) { +// td::protocol::CommandType commandType = sp::protocol::CommandType(i); + +// if (td::protocol::CommandFactory::CreateReadOnlyCommand(commandType)->GetType() != commandType) +// return SP_TEST_FAILED; +// } +// return SP_TEST_SUCCESSFUL; +// } + +// int main() { +// return Test(); +// } \ No newline at end of file diff --git a/test/sp/protocol/command/CommandSerializer_test.cpp b/test/sp/protocol/command/CommandSerializer_test.cpp new file mode 100644 index 0000000..41f59fd --- /dev/null +++ b/test/sp/protocol/command/CommandSerializer_test.cpp @@ -0,0 +1,96 @@ +// #include + +// #include +// #include + +// namespace tp = sp::protocol; + +// class Comparator { +// public: +// bool operator()(const tp::cdata::End& left, const tp::cdata::End& right) { +// return true; +// } +// bool operator()(const tp::cdata::PlaceTower& left, const tp::cdata::PlaceTower& right) { +// return left.m_Placer == right.m_Placer && left.m_Position.x == right.m_Position.x && left.m_Position.y == right.m_Position.y && +// left.m_Type == right.m_Type; +// } +// bool operator()(const tp::cdata::PlayerJoin& left, const tp::cdata::PlayerJoin& right) { +// return left.m_ID == right.m_ID && left.m_Name == right.m_Name; +// } +// bool operator()(const tp::cdata::SpawnTroop& left, const tp::cdata::SpawnTroop& right) { +// return left.m_Level == right.m_Level && left.m_Position.x == right.m_Position.x && left.m_Position.y == right.m_Position.y && +// left.m_Sender == right.m_Sender && left.m_Type == right.m_Type; +// } +// bool operator()(const tp::cdata::TeamChange& left, const tp::cdata::TeamChange& right) { +// return left.m_NewTeam == right.m_NewTeam && left.m_Player == right.m_Player; +// } +// bool operator()(const tp::cdata::UpgradeTower& left, const tp::cdata::UpgradeTower& right) { +// return left.m_Tower == right.m_Tower && left.m_Upgrade == right.m_Upgrade; +// } +// bool operator()(const tp::cdata::UseItem& left, const tp::cdata::UseItem& right) { +// return left.m_Item == right.m_Item && left.m_Position.x == right.m_Position.x && left.m_Position.y == right.m_Position.y && +// left.m_User == right.m_User; +// } +// }; + +// template +// static int TestCommand(const Command_T& command) { + +// td::DataBuffer buffer = tp::CommandSerializer::Serialize(command); + +// auto abstractCommand = tp::CommandSerializer::Deserialize(buffer); + +// td_test_assert(abstractCommand); + +// Command_T* command2 = dynamic_cast(abstractCommand.get()); + +// td_test_assert(command2); +// td_test_assert(command.GetType() == command2->GetType()); + +// return Comparator{}(command.m_Data, command2->m_Data); +// } + +// #define DeclareCommand(Command, ...) sp_test_assert(TestCommand({})); + +// static int TestAllCommands() { +// td_test_assert(TestCommand({})); +// td_test_assert(TestCommand({tp::cdata::PlaceTower{ +// td::TowerType::Necromancer, +// 8, +// td::TowerCoords{-50, 69}, +// }})); +// td_test_assert(TestCommand({tp::cdata::PlayerJoin{ +// 4, +// "Persson", +// }})); +// td_test_assert(TestCommand({tp::cdata::SpawnTroop{ +// td::EntityType::Blaze, +// 4, +// td::EntityCoords{td::FpFloat{54}, sp::FpFloat{58}}, +// 2, +// }})); +// td_test_assert(TestCommand({tp::cdata::TeamChange{ +// 7, +// td::Team::Red, +// }})); +// td_test_assert(TestCommand({tp::cdata::UpgradeTower{ +// 69, +// 3, +// }})); +// td_test_assert(TestCommand({tp::cdata::UseItem{ +// td::ShopItem::Freeze, +// 5, +// td::EntityCoords{td::FpFloat{24}, sp::FpFloat{-69}}, +// }})); + +// DeclareAllCommand() + +// return SP_TEST_SUCCESSFUL; +// } + +// int main() { +// return TestAllCommands(); +// } +int main() { + return 0; +} \ No newline at end of file diff --git a/test/sp/protocol/packet/PacketFactory_test.cpp b/test/sp/protocol/packet/PacketFactory_test.cpp new file mode 100644 index 0000000..e9d7306 --- /dev/null +++ b/test/sp/protocol/packet/PacketFactory_test.cpp @@ -0,0 +1,17 @@ +// #include + +// #include + +// static int Test() { +// for (std::size_t i = 0; i < static_cast(td::protocol::PacketType::PACKET_COUNT); i++) { +// td::protocol::PacketType packetType = sp::protocol::PacketType(i); + +// if (td::protocol::PacketFactory::CreateReadOnlyPacket(packetType)->GetType() != packetType) +// return SP_TEST_FAILED; +// } +// return SP_TEST_SUCCESSFUL; +// } + +// int main() { +// return Test(); +// } \ No newline at end of file diff --git a/test/sp/protocol/packet/PacketSerializer_test.cpp b/test/sp/protocol/packet/PacketSerializer_test.cpp new file mode 100644 index 0000000..75d5e42 --- /dev/null +++ b/test/sp/protocol/packet/PacketSerializer_test.cpp @@ -0,0 +1,36 @@ +// #include + +// #include +// #include + +// namespace tp = sp::protocol; + +// template +// static int TestPacket() { +// Packet_T packet; + +// td::DataBuffer buffer = tp::PacketSerializer::Serialize(packet); + +// auto abstractPacket = tp::PacketSerializer::Deserialize(buffer); + +// td_test_assert(abstractPacket); + +// Packet_T* packet2 = dynamic_cast(abstractPacket.get()); + +// td_test_assert(packet2); +// td_test_assert(packet.GetType() == packet2->GetType()); + +// return std::memcmp(&packet.m_Data, &packet2->m_Data, sizeof(Packet_Data_T)); +// } + +// #define DeclarePacket(Packet, ...) TestPacket(); + +// static int TestAllPackets() { +// DeclareAllPacket() + +// return TD_TEST_SUCCESSFUL; +// } + +// int main() { +// return TestAllPackets(); +// } \ No newline at end of file diff --git a/xmake.lua b/xmake.lua new file mode 100644 index 0000000..7f9c2c6 --- /dev/null +++ b/xmake.lua @@ -0,0 +1,97 @@ +add_rules("mode.debug", "mode.release") + +set_languages("c++17") + +add_requires("enet6") + +target("SimpleProtocolLib") + add_includedirs("include", {public = true}) + set_kind("static") + add_files("src/**.cpp") + add_packages("enet6", {public = true}) + + + +-- Tests +for _, file in ipairs(os.files("test/**.cpp")) do + local name = path.basename(file) + target(name) + set_kind("binary") + + add_files(file) + + set_default(false) + + add_deps("SimpleProtocolLib") + + add_tests("compile_and_run") +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 +-- +