first commit
This commit is contained in:
37
.clang-format
Normal file
37
.clang-format
Normal file
@@ -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
|
||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Xmake cache
|
||||
.xmake/
|
||||
build/
|
||||
|
||||
# MacOS Cache
|
||||
.DS_Store
|
||||
|
||||
|
||||
.vscode
|
||||
9
include/sp/Types.h
Normal file
9
include/sp/Types.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace sp {
|
||||
|
||||
using PeerID = std::uint16_t;
|
||||
|
||||
} // namespace sp
|
||||
300
include/sp/common/DataBuffer.h
Normal file
300
include/sp/common/DataBuffer.h
Normal file
@@ -0,0 +1,300 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file DataBuffer.h
|
||||
* \brief File containing the sp::DataBuffer class
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <sp/common/VarInt.h>
|
||||
#include <vector>
|
||||
|
||||
namespace sp {
|
||||
|
||||
/**
|
||||
* \class DataBuffer
|
||||
* \brief Class used to manipulate memory
|
||||
*/
|
||||
class DataBuffer {
|
||||
private:
|
||||
typedef std::vector<std::uint8_t> 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 <typename T>
|
||||
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 <typename T>
|
||||
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 <typename T>
|
||||
DataBuffer& operator<<(const std::vector<T>& 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 <typename T, std::size_t Size>
|
||||
DataBuffer& operator<<(const std::array<T, Size>& data) {
|
||||
for (const auto& element : data) {
|
||||
*this << element;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read some data from the buffer and assign to desired variable
|
||||
*/
|
||||
template <typename T>
|
||||
DataBuffer& operator>>(T& data) {
|
||||
assert(m_ReadOffset + sizeof(T) <= GetSize());
|
||||
data = *(reinterpret_cast<T*>(&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 <typename T>
|
||||
DataBuffer& operator>>(std::vector<T>& 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 <std::size_t Size, typename T>
|
||||
DataBuffer& operator>>(std::array<T, Size>& 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
|
||||
25
include/sp/common/NonCopyable.h
Normal file
25
include/sp/common/NonCopyable.h
Normal file
@@ -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
|
||||
58
include/sp/common/VarInt.h
Normal file
58
include/sp/common/VarInt.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file VarInt.h
|
||||
* \brief File containing the sp::VarInt class
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
38
include/sp/misc/Format.h
Normal file
38
include/sp/misc/Format.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#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
|
||||
32
include/sp/misc/Log.h
Normal file
32
include/sp/misc/Log.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#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
|
||||
70
include/sp/misc/Test.h
Normal file
70
include/sp/misc/Test.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#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
|
||||
61
include/sp/protocol/Dispatcher.h
Normal file
61
include/sp/protocol/Dispatcher.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file PacketDispatcher.h
|
||||
* \brief File containing the sp::protocol::PacketDispatcher class
|
||||
*/
|
||||
|
||||
#include <sp/common/NonCopyable.h>
|
||||
#include <sp/protocol/packet/Packets.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
/**
|
||||
* \class Dispatcher
|
||||
* \brief Class used to dispatch things
|
||||
*/
|
||||
template <typename T_Enum, typename T_Handler, typename T>
|
||||
class Dispatcher : private NonCopyable {
|
||||
private:
|
||||
std::map<T_Enum, std::vector<T_Handler*>> 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"
|
||||
38
include/sp/protocol/Dispatcher.inl
Normal file
38
include/sp/protocol/Dispatcher.inl
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
template <typename T_Enum, typename T_Handler, typename T>
|
||||
void Dispatcher<T_Enum, T_Handler, T>::Dispatch(const T& packet) {
|
||||
T_Enum type = packet.GetType();
|
||||
for (auto* handler : m_Handlers[type])
|
||||
handler->Check(packet);
|
||||
}
|
||||
|
||||
template <typename T_Enum, typename T_Handler, typename T>
|
||||
void Dispatcher<T_Enum, T_Handler, T>::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 <typename T_Enum, typename T_Handler, typename T>
|
||||
void Dispatcher<T_Enum, T_Handler, T>::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 <typename T_Enum, typename T_Handler, typename T>
|
||||
void Dispatcher<T_Enum, T_Handler, T>::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
|
||||
53
include/sp/protocol/command/CommandData.h
Normal file
53
include/sp/protocol/command/CommandData.h
Normal file
@@ -0,0 +1,53 @@
|
||||
// #pragma once
|
||||
|
||||
// #include <array>
|
||||
// #include <string>
|
||||
// #include <sp/Types.h>
|
||||
|
||||
// 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
|
||||
22
include/sp/protocol/command/CommandDeclare.h
Normal file
22
include/sp/protocol/command/CommandDeclare.h
Normal file
@@ -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
|
||||
17
include/sp/protocol/command/CommandDispatcher.h
Normal file
17
include/sp/protocol/command/CommandDispatcher.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file CommandDispatcher.h
|
||||
* \brief File containing the sp::protocol::CommandDispatcher class
|
||||
*/
|
||||
|
||||
#include <sp/protocol/Dispatcher.h>
|
||||
#include <sp/protocol/command/Commands.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
using CommandDispatcher = Dispatcher<CommandType, CommandVisitor, Command>;
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
19
include/sp/protocol/command/CommandFactory.h
Normal file
19
include/sp/protocol/command/CommandFactory.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sp/protocol/command/Commands.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
namespace CommandFactory {
|
||||
|
||||
template <typename CommandDerived, typename = typename std::enable_if<std::is_base_of<Command, CommandDerived>::value>::type>
|
||||
std::shared_ptr<CommandDerived> CreateCommand() {
|
||||
return std::make_shared<CommandDerived>();
|
||||
}
|
||||
|
||||
const std::shared_ptr<Command>& CreateReadOnlyCommand(CommandType a_Type);
|
||||
|
||||
} // namespace CommandFactory
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
21
include/sp/protocol/command/CommandSerializer.h
Normal file
21
include/sp/protocol/command/CommandSerializer.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sp/common/DataBuffer.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
class Command;
|
||||
|
||||
using CommandPtr = std::shared_ptr<Command>;
|
||||
|
||||
namespace CommandSerializer {
|
||||
|
||||
DataBuffer Serialize(const Command& a_Command);
|
||||
|
||||
std::shared_ptr<Command> Deserialize(DataBuffer& a_Data);
|
||||
|
||||
} // namespace CommandSerializer
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
39
include/sp/protocol/command/CommandVisitor.h
Normal file
39
include/sp/protocol/command/CommandVisitor.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file CommandVisitor.h
|
||||
* \brief File containing the sp::protocol::CommandVisitor class
|
||||
*/
|
||||
|
||||
#include <sp/protocol/command/Commands.h>
|
||||
|
||||
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
|
||||
111
include/sp/protocol/command/Commands.h
Normal file
111
include/sp/protocol/command/Commands.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file Commands.h
|
||||
* \brief File containing the definitions of the lockstep commands
|
||||
*/
|
||||
|
||||
#include <sp/Types.h>
|
||||
#include <sp/common/NonCopyable.h>
|
||||
#include <sp/protocol/command/CommandData.h>
|
||||
#include <sp/protocol/command/CommandDeclare.h>
|
||||
#include <memory>
|
||||
|
||||
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 <CommandType CT, typename Data>
|
||||
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<CommandType::CommandName, cdata::CommandName>; \
|
||||
template class ConcreteCommand<CommandType::CommandName, cdata::CommandName>;
|
||||
#else
|
||||
#define DeclareCommand(CommandName, ...) /** Defines the CommandName Command */ \
|
||||
using CommandName = ConcreteCommand<CommandType::CommandName, cdata::CommandName>;
|
||||
#endif
|
||||
|
||||
DeclareAllCommand()
|
||||
|
||||
#undef DeclareCommand
|
||||
|
||||
} // namespace commands
|
||||
|
||||
using LockStep = std::vector<std::shared_ptr<protocol::Command>>;
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
76
include/sp/protocol/packet/PacketData.h
Normal file
76
include/sp/protocol/packet/PacketData.h
Normal file
@@ -0,0 +1,76 @@
|
||||
// #pragma once
|
||||
|
||||
// #include <td/Types.h>
|
||||
// #include <vector>
|
||||
// #include <td/protocol/command/Commands.h>
|
||||
|
||||
// // 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<PlayerInfo> m_BlueTeam;
|
||||
// std::vector<PlayerInfo> m_RedTeam;
|
||||
// // optional, used for players joining mid game
|
||||
// std::vector<LockStep> m_FirstLocksteps;
|
||||
// };
|
||||
|
||||
// struct LockSteps {
|
||||
// std::uint16_t m_FirstFrameNumber;
|
||||
// std::array<LockStep, LOCKSTEP_BUFFER_SIZE> m_LockSteps;
|
||||
// };
|
||||
|
||||
// } // namespace pdata
|
||||
// } // namespace protocol
|
||||
// } // namespace sp
|
||||
48
include/sp/protocol/packet/PacketDeclare.h
Normal file
48
include/sp/protocol/packet/PacketDeclare.h
Normal file
@@ -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
|
||||
17
include/sp/protocol/packet/PacketDispatcher.h
Normal file
17
include/sp/protocol/packet/PacketDispatcher.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file PacketDispatcher.h
|
||||
* \brief File containing the sp::protocol::PacketDispatcher class
|
||||
*/
|
||||
|
||||
#include <sp/protocol/Dispatcher.h>
|
||||
#include <sp/protocol/packet/Packets.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
using PacketDispatcher = Dispatcher<PacketType, PacketVisitor, Packet>;
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
19
include/sp/protocol/packet/PacketFactory.h
Normal file
19
include/sp/protocol/packet/PacketFactory.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sp/protocol/packet/Packets.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
namespace PacketFactory {
|
||||
|
||||
template <typename PacketDerived, typename = typename std::enable_if<std::is_base_of<Packet, PacketDerived>::value>::type>
|
||||
std::unique_ptr<PacketDerived> CreatePacket() {
|
||||
return std::make_unique<PacketDerived>();
|
||||
}
|
||||
|
||||
const std::unique_ptr<Packet>& CreateReadOnlyPacket(PacketType a_Type);
|
||||
|
||||
} // namespace PacketFactory
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
21
include/sp/protocol/packet/PacketSerializer.h
Normal file
21
include/sp/protocol/packet/PacketSerializer.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sp/common/DataBuffer.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
class Packet;
|
||||
|
||||
using PacketPtr = std::unique_ptr<Packet>;
|
||||
|
||||
namespace PacketSerializer {
|
||||
|
||||
DataBuffer Serialize(const Packet& a_Packet);
|
||||
|
||||
std::unique_ptr<Packet> Deserialize(DataBuffer& a_Data);
|
||||
|
||||
} // namespace PacketSerializer
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
39
include/sp/protocol/packet/PacketVisitor.h
Normal file
39
include/sp/protocol/packet/PacketVisitor.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file PacketVisitor.h
|
||||
* \brief File containing the sp::protocol::PacketVisitor class
|
||||
*/
|
||||
|
||||
#include <sp/protocol/packet/Packets.h>
|
||||
|
||||
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
|
||||
115
include/sp/protocol/packet/Packets.h
Normal file
115
include/sp/protocol/packet/Packets.h
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file Packets.h
|
||||
* \brief File containing the definitions of the packets
|
||||
*/
|
||||
|
||||
#include <sp/common/NonCopyable.h>
|
||||
#include <sp/protocol/packet/PacketData.h>
|
||||
#include <sp/protocol/packet/PacketDeclare.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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 <PacketType PT, typename Data>
|
||||
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<PacketType::PacketName, pdata::PacketName>; \
|
||||
template class ConcretePacket<PacketType::PacketName, pdata::PacketName>;
|
||||
#else
|
||||
#define DeclarePacket(PacketName, ...) /** Defines the PacketName packet */ \
|
||||
using PacketName = ConcretePacket<PacketType::PacketName, pdata::PacketName>;
|
||||
#endif
|
||||
|
||||
// DeclareAllPacket()
|
||||
|
||||
#undef DeclarePacket
|
||||
|
||||
} // namespace packets
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
21
src/main.cpp
Normal file
21
src/main.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include <iostream>
|
||||
// #include <sp/protocol/packet/PacketVisitor.h>
|
||||
#include <sp/game/GameHistory.h>
|
||||
#include <sp/protocol/command/CommandDispatcher.h>
|
||||
#include <sp/protocol/command/CommandFactory.h>
|
||||
#include <sp/protocol/command/CommandSerializer.h>
|
||||
#include <sp/protocol/command/CommandVisitor.h>
|
||||
|
||||
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;
|
||||
}
|
||||
163
src/sp/common/DataBuffer.cpp
Normal file
163
src/sp/common/DataBuffer.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include <sp/common/DataBuffer.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include <sp/misc/Format.h>
|
||||
#include <sp/misc/Log.h>
|
||||
|
||||
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<std::size_t>(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<const char*>(m_Buffer.data()) + m_ReadOffset) + 1; // including null character
|
||||
str.resize(stringSize);
|
||||
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset),
|
||||
m_Buffer.begin() + static_cast<difference_type>(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<difference_type>(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<difference_type>(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<difference_type>(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<difference_type>(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<int>(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<const char*>(m_Buffer.data()), static_cast<std::streamsize>(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
|
||||
52
src/sp/common/VarInt.cpp
Normal file
52
src/sp/common/VarInt.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <sp/common/VarInt.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <sp/common/DataBuffer.h>
|
||||
|
||||
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<std::uint64_t>(SEGMENT_BITS)) == 0) {
|
||||
out << static_cast<std::uint8_t>(value);
|
||||
return out;
|
||||
}
|
||||
|
||||
out << static_cast<std::uint8_t>((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<std::uint64_t>(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
|
||||
37
src/sp/misc/Log.cpp
Normal file
37
src/sp/misc/Log.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#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
|
||||
14
src/sp/misc/Test.cpp
Normal file
14
src/sp/misc/Test.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#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
|
||||
24
src/sp/protocol/command/CommandFactory.cpp
Normal file
24
src/sp/protocol/command/CommandFactory.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <sp/protocol/command/CommandFactory.h>
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
namespace CommandFactory {
|
||||
|
||||
using CommandCreator = std::function<std::shared_ptr<Command>()>;
|
||||
|
||||
#define DeclareCommand(CommandName, ...) std::make_shared<commands::CommandName>(),
|
||||
|
||||
static std::array<std::shared_ptr<Command>, static_cast<std::size_t>(CommandType::COMMAND_COUNT)> Commands = {DeclareAllCommand()};
|
||||
|
||||
const std::shared_ptr<Command>& CreateReadOnlyCommand(CommandType a_Type) {
|
||||
assert(a_Type < CommandType::COMMAND_COUNT);
|
||||
return Commands[static_cast<std::size_t>(a_Type)];
|
||||
}
|
||||
|
||||
} // namespace CommandFactory
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
229
src/sp/protocol/command/CommandSerializer.cpp
Normal file
229
src/sp/protocol/command/CommandSerializer.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#include <sp/protocol/command/CommandSerializer.h>
|
||||
|
||||
#include <sp/protocol/command/CommandFactory.h>
|
||||
#include <sp/protocol/command/CommandVisitor.h>
|
||||
|
||||
#include <sp/misc/Format.h>
|
||||
#include <sp/misc/Log.h>
|
||||
|
||||
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<CommandID>(a_Command.GetType());
|
||||
Check(a_Command);
|
||||
}
|
||||
|
||||
DeclareAllCommand()
|
||||
};
|
||||
|
||||
#undef DeclareCommand
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define DeclareCommand(CommandName, ...) \
|
||||
void Visit(const commands::CommandName& a_Command) override { \
|
||||
auto commandPtr = CommandFactory::CreateCommand<commands::CommandName>(); \
|
||||
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<Command> Deserialize(DataBuffer& a_Data) {
|
||||
CommandID commandId;
|
||||
a_Data >> commandId;
|
||||
|
||||
if (commandId >= static_cast<CommandID>(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<std::uint8_t>((static_cast<std::uint8_t>(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<std::uint8_t>(static_cast<std::uint8_t>(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<std::uint8_t>(a_Command.m_Player << 1 | static_cast<std::uint8_t>(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<std::uint16_t>(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<std::uint8_t>(static_cast<std::uint8_t>(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
|
||||
11
src/sp/protocol/command/CommandVisitor.cpp
Normal file
11
src/sp/protocol/command/CommandVisitor.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <sp/protocol/command/CommandVisitor.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
void CommandVisitor::Check(const Command& a_Command) {
|
||||
a_Command.Accept(*this);
|
||||
}
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
18
src/sp/protocol/command/Commands.cpp
Normal file
18
src/sp/protocol/command/Commands.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#define SP_INSTANCIATE_COMMANDS
|
||||
#include <sp/protocol/command/Commands.h>
|
||||
|
||||
#include <sp/protocol/command/CommandVisitor.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
template <CommandType CT, typename Data>
|
||||
commands::ConcreteCommand<CT, Data>::ConcreteCommand(const CommandDataType& a_Data) : m_Data(a_Data) {}
|
||||
|
||||
template <CommandType CT, typename Data>
|
||||
void commands::ConcreteCommand<CT, Data>::Accept(CommandVisitor& a_Visitor) const {
|
||||
a_Visitor.Visit(*this);
|
||||
}
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
26
src/sp/protocol/packet/PacketFactory.cpp
Normal file
26
src/sp/protocol/packet/PacketFactory.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <sp/protocol/packet/PacketFactory.h>
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
namespace PacketFactory {
|
||||
|
||||
using PacketCreator = std::function<std::unique_ptr<Packet>()>;
|
||||
|
||||
#define DeclarePacket(PacketName, ...) std::make_unique<packets::PacketName>(),
|
||||
|
||||
static std::array<std::unique_ptr<Packet>, static_cast<std::size_t>(PacketType::PACKET_COUNT)> Packets = {
|
||||
DeclareAllPacket()
|
||||
};
|
||||
|
||||
const std::unique_ptr<Packet>& CreateReadOnlyPacket(PacketType a_Type) {
|
||||
assert(a_Type < PacketType::PACKET_COUNT);
|
||||
return Packets[static_cast<std::size_t>(a_Type)];
|
||||
}
|
||||
|
||||
} // namespace PacketFactory
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
262
src/sp/protocol/packet/PacketSerializer.cpp
Normal file
262
src/sp/protocol/packet/PacketSerializer.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
#include <sp/protocol/packet/PacketSerializer.h>
|
||||
|
||||
#include <sp/protocol/command/CommandSerializer.h>
|
||||
#include <sp/protocol/packet/PacketFactory.h>
|
||||
#include <sp/protocol/packet/PacketVisitor.h>
|
||||
|
||||
#include <sp/misc/Format.h>
|
||||
#include <sp/misc/Log.h>
|
||||
|
||||
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<PacketID>(a_Packet.GetType());
|
||||
Check(a_Packet);
|
||||
}
|
||||
|
||||
DeclareAllPacket()
|
||||
};
|
||||
|
||||
#undef DeclarePacket
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define DeclarePacket(PacketName, ...) \
|
||||
void Visit(const packets::PacketName& a_Packet) override { \
|
||||
auto packetPtr = PacketFactory::CreatePacket<packets::PacketName>(); \
|
||||
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<Packet> Deserialize(DataBuffer& a_Data) {
|
||||
PacketID packetId;
|
||||
a_Data >> packetId;
|
||||
|
||||
if (packetId >= static_cast<PacketID>(PacketType::PACKET_COUNT))
|
||||
return nullptr;
|
||||
|
||||
PacketType packetType = PacketType(packetId);
|
||||
|
||||
// for double-dispatch
|
||||
const PacketPtr& emptyPacket = PacketFactory::CreateReadOnlyPacket(packetType);
|
||||
|
||||
Deserializer deserializer(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
|
||||
11
src/sp/protocol/packet/PacketVisitor.cpp
Normal file
11
src/sp/protocol/packet/PacketVisitor.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <sp/protocol/packet/PacketVisitor.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
void PacketVisitor::Check(const Packet& a_Packet) {
|
||||
a_Packet.Accept(*this);
|
||||
}
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
23
src/sp/protocol/packet/Packets.cpp
Normal file
23
src/sp/protocol/packet/Packets.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#define SP_INSTANCIATE_PACKETS
|
||||
#include <sp/protocol/packet/Packets.h>
|
||||
|
||||
#include <sp/protocol/packet/PacketVisitor.h>
|
||||
|
||||
namespace sp {
|
||||
namespace protocol {
|
||||
|
||||
template <PacketType PT, typename Data>
|
||||
packets::ConcretePacket<PT, Data>::ConcretePacket(const PacketDataType& a_Data) : m_Data(a_Data) {}
|
||||
|
||||
template <PacketType PT, typename Data>
|
||||
void packets::ConcretePacket<PT, Data>::Accept(PacketVisitor& a_Visitor) const {
|
||||
a_Visitor.Visit(*this);
|
||||
}
|
||||
|
||||
#define DeclarePacket(PacketName, packetSendType, packetSenderType) \
|
||||
static_assert(static_cast<unsigned>(PacketSendType::packetSendType) && static_cast<unsigned>(PacketSenderType::packetSenderType));
|
||||
|
||||
DeclareAllPacket()
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace sp
|
||||
17
test/sp/protocol/command/CommandFactory_test.cpp
Normal file
17
test/sp/protocol/command/CommandFactory_test.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
// #include <sp/protocol/command/CommandFactory.h>
|
||||
|
||||
// #include <sp/misc/Test.h>
|
||||
|
||||
// static int Test() {
|
||||
// for (std::size_t i = 0; i < static_cast<int>(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();
|
||||
// }
|
||||
96
test/sp/protocol/command/CommandSerializer_test.cpp
Normal file
96
test/sp/protocol/command/CommandSerializer_test.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
// #include <sp/protocol/command/CommandSerializer.h>
|
||||
|
||||
// #include <sp/misc/Test.h>
|
||||
// #include <sp/protocol/command/CommandFactory.h>
|
||||
|
||||
// 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 <typename Command_T, typename Command_Data_T = typename Command_T::CommandDataType>
|
||||
// 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<Command_T*>(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<tp::commands::Command>({}));
|
||||
|
||||
// static int TestAllCommands() {
|
||||
// td_test_assert(TestCommand<tp::commands::End>({}));
|
||||
// td_test_assert(TestCommand<tp::commands::PlaceTower>({tp::cdata::PlaceTower{
|
||||
// td::TowerType::Necromancer,
|
||||
// 8,
|
||||
// td::TowerCoords{-50, 69},
|
||||
// }}));
|
||||
// td_test_assert(TestCommand<tp::commands::PlayerJoin>({tp::cdata::PlayerJoin{
|
||||
// 4,
|
||||
// "Persson",
|
||||
// }}));
|
||||
// td_test_assert(TestCommand<tp::commands::SpawnTroop>({tp::cdata::SpawnTroop{
|
||||
// td::EntityType::Blaze,
|
||||
// 4,
|
||||
// td::EntityCoords{td::FpFloat{54}, sp::FpFloat{58}},
|
||||
// 2,
|
||||
// }}));
|
||||
// td_test_assert(TestCommand<tp::commands::TeamChange>({tp::cdata::TeamChange{
|
||||
// 7,
|
||||
// td::Team::Red,
|
||||
// }}));
|
||||
// td_test_assert(TestCommand<tp::commands::UpgradeTower>({tp::cdata::UpgradeTower{
|
||||
// 69,
|
||||
// 3,
|
||||
// }}));
|
||||
// td_test_assert(TestCommand<tp::commands::UseItem>({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;
|
||||
}
|
||||
17
test/sp/protocol/packet/PacketFactory_test.cpp
Normal file
17
test/sp/protocol/packet/PacketFactory_test.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
// #include <sp/protocol/packet/PacketFactory.h>
|
||||
|
||||
// #include <sp/misc/Test.h>
|
||||
|
||||
// static int Test() {
|
||||
// for (std::size_t i = 0; i < static_cast<int>(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();
|
||||
// }
|
||||
36
test/sp/protocol/packet/PacketSerializer_test.cpp
Normal file
36
test/sp/protocol/packet/PacketSerializer_test.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// #include <sp/protocol/packet/PacketSerializer.h>
|
||||
|
||||
// #include <sp/misc/Test.h>
|
||||
// #include <sp/protocol/packet/PacketFactory.h>
|
||||
|
||||
// namespace tp = sp::protocol;
|
||||
|
||||
// template <typename Packet_T, typename Packet_Data_T = typename Packet_T::PacketDataType>
|
||||
// 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<Packet_T*>(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<tp::packets::Packet>();
|
||||
|
||||
// static int TestAllPackets() {
|
||||
// DeclareAllPacket()
|
||||
|
||||
// return TD_TEST_SUCCESSFUL;
|
||||
// }
|
||||
|
||||
// int main() {
|
||||
// return TestAllPackets();
|
||||
// }
|
||||
97
xmake.lua
Normal file
97
xmake.lua
Normal file
@@ -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
|
||||
--
|
||||
|
||||
Reference in New Issue
Block a user