Changeset View
Standalone View
source/network/NetMessageBuffer.h
- This file was added.
/* Copyright (C) 2017 Wildfire Games. | |||||
* This file is part of 0 A.D. | |||||
* | |||||
* 0 A.D. is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 2 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* 0 A.D. is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef NETMESSAGEBUFFER_H | |||||
#define NETMESSAGEBUFFER_H | |||||
leper: You might want to update that for the new filename. | |||||
#include "NetMessageType.h" | |||||
#include "ps/CStr.h" | |||||
#include "scriptinterface/ScriptInterface.h" | |||||
#include <type_traits> | |||||
/** | |||||
* This file deals with storing net messages in a serialized state, | |||||
* and serializing / deserializing them. Note that in this file, | |||||
* a "serialization primitive" refers to one of: | |||||
* - an integer | |||||
* - std::vector | |||||
* - std::array | |||||
* - CStr or CStrW | |||||
* - SerializableJSValue | |||||
* whereas a compound object refers to a structure potentially containing multiple objects of these types which | |||||
* can be serialized (and inherits from ISerializable. | |||||
*/ | |||||
/** | |||||
* Should be used to tag objects that define a const and non-const RegisterSerializable function of signatures: | |||||
* @code | |||||
* template<typename Func> | |||||
* struct RegisterSerializable(Func func); | |||||
* template<typename Func> | |||||
* struct RegisterSerializable(Func func) const; | |||||
* @endcode | |||||
* which executes the parameter on every member which needs to be serialized. | |||||
* It is recommended to use the REGISTER_SERIALIZABLE(...) and REGISTER_SERIALIZABLE_EMPTY() to generate these | |||||
* functions. | |||||
*/ | |||||
class ISerializable {}; | |||||
Not Done Inline ActionsAh, that's just very close to what we already have in the simulation, but not a name collision. leper: Ah, that's just very close to what we already have in the simulation, but not a name collision. | |||||
/** | |||||
* Defines both the constant and non constant version of the RegisterSerializable functions. | |||||
*/ | |||||
Not Done Inline ActionsWhere do we need the non-const one? leper: Where do we need the non-const one? | |||||
Not Done Inline ActionsThe non-const version is used during deserialization (the members are passed by non-const reference to the deserialization functions). echotangoecho: The non-const version is used during deserialization (the members are passed by non-const… | |||||
#define REGISTER_SERIALIZABLE(...) \ | |||||
Done Inline ActionsThat comment seems pointless. leper: That comment seems pointless. | |||||
template<typename Func> \ | |||||
void RegisterSerializable(Func registerFunction) { registerFunction(__VA_ARGS__); } \ | |||||
template<typename Func> \ | |||||
void RegisterSerializable(Func registerFunction) const { registerFunction(__VA_ARGS__); } | |||||
/** | |||||
* Does the same thing as REGISTER_SERIALIZABLE, but doesn't serialize any members of the class | |||||
* (that is, only the size and type will be serialized). | |||||
*/ | |||||
#define REGISTER_SERIALIZABLE_EMPTY() \ | |||||
template<typename Func> \ | |||||
void RegisterSerializable(Func) {} \ | |||||
template<typename Func> \ | |||||
void RegisterSerializable(Func) const {} | |||||
#define BEGIN_SERIALIZABLE(typeName) \ | |||||
struct typeName : ISerializable { \ | |||||
#define END_SERIALIZABLE() }; | |||||
#define BEGIN_NETMESSAGE_WITH_CUSTOM_CONSTRUCTOR(typeName, nmType) \ | |||||
class typeName : ISerializable { \ | |||||
public: \ | |||||
static const NetMessageType type = nmType; | |||||
#define END_NETMESSAGE_WITH_CUSTOM_CONSTRUCTOR() }; | |||||
#define BEGIN_NETMESSAGE(typeName, nmType) \ | |||||
class typeName : ISerializable { \ | |||||
public: \ | |||||
static const NetMessageType type = nmType; \ | |||||
typeName() {} \ | |||||
typeName(const CNetMessageBuffer& buffer) { buffer.GetMessage(*this); } | |||||
#define END_NETMESSAGE() }; | |||||
struct SerializableJSValue | |||||
{ | |||||
SerializableJSValue(ScriptInterface& interface) : | |||||
m_ScriptInterface(interface), | |||||
m_Data(interface.GetJSRuntime()) | |||||
{ | |||||
} | |||||
SerializableJSValue(ScriptInterface& interface, JS::HandleValue data) : | |||||
m_ScriptInterface(interface), | |||||
m_Data(interface.GetJSRuntime(), data) | |||||
{ | |||||
} | |||||
ScriptInterface& m_ScriptInterface; | |||||
JS::PersistentRootedValue m_Data; | |||||
}; | |||||
/** | |||||
* Represents a serialized net message. The SerializableObject type used in it | |||||
* refers to the class type locally representing the message, and it is expected to derive from ISerializable. | |||||
* Additionally it is expected to contain a static constant "type" which holds the associated NetMessageType. | |||||
*/ | |||||
class CNetMessageBuffer | |||||
{ | |||||
public: | |||||
CNetMessageBuffer(); | |||||
/** | |||||
* Serializes the given object into the buffer. | |||||
*/ | |||||
template<typename SerializableObject> | |||||
CNetMessageBuffer(const SerializableObject& object) | |||||
{ | |||||
m_Type = object.type; | |||||
Done Inline ActionsAre there many occurences where we need a copy of this? leper: Are there many occurences where we need a copy of this? | |||||
Not Done Inline Actionsonly in one place, I have added a data() and size() function for that. echotangoecho: only in one place, I have added a data() and size() function for that. | |||||
// The first 5 bytes of the buffer are reserved for the type (1 byte) and the size (4 bytes) of the message. | |||||
ToPosition(HEADER_SIZE); | |||||
SerializeCompoundFunctor serializer{*this}; | |||||
serializer(object); | |||||
ToPosition(0); | |||||
SerializePrimitive((u8)object.type); | |||||
Done Inline ActionsCould be const leper: Could be const | |||||
SerializePrimitive((u32)m_Buffer.size()); | |||||
} | |||||
CNetMessageBuffer(const std::vector<u8>& buffer); | |||||
CNetMessageBuffer(u8* buffer, u32 size); | |||||
NetMessageType GetType() const | |||||
{ | |||||
return m_Type; | |||||
} | |||||
const u8* GetData() const | |||||
Done Inline ActionsMight want to make this explicit, in case someone adds some operators, and also because so far I don't think I've seen any usage in this diff that would need changes. leper: Might want to make this `explicit`, in case someone adds some operators, and also because so… | |||||
{ | |||||
ENSURE(!m_Buffer.empty()); | |||||
return m_Buffer.data(); | |||||
} | |||||
size_t GetSize() const | |||||
{ | |||||
return m_Buffer.size(); | |||||
} | |||||
/** | |||||
* Does this buffer hold a valid message? | |||||
*/ | |||||
explicit operator bool() const | |||||
{ | |||||
return m_Type != NMT_INVALID && m_Buffer.size() >= HEADER_SIZE; | |||||
} | |||||
/** | |||||
* Deserializes the buffer into the given object. | |||||
*/ | |||||
template<typename SerializableObject> | |||||
void GetMessage(SerializableObject& object) const | |||||
{ | |||||
ENSURE(object.type == m_Type); | |||||
ToPosition(1); | |||||
u32 size; | |||||
DeserializePrimitive(size); | |||||
ENSURE(size == m_Buffer.size()); | |||||
DeserializeCompoundFunctor deserializer{*this}; | |||||
deserializer(object); | |||||
} | |||||
private: | |||||
void ToPosition(u32 pos); | |||||
/** | |||||
Not Done Inline ActionsAt least follow the example in the simulation serialization code and fail explicitly in case anyone ever tries to build on something big endian. leper: At least follow the example in the simulation serialization code and fail explicitly in case… | |||||
Not Done Inline ActionsI don't think this code depends on endianness? I just serialize the integer to little-endian here, and deserialize it as such. echotangoecho: I don't think this code depends on endianness? I just serialize the integer to little-endian… | |||||
Not Done Inline ActionsAh, right. Sorry for the noise. leper: Ah, right. Sorry for the noise. | |||||
* const as it doesn't allow enlarging the buffer. | |||||
*/ | |||||
void ToPosition(u32 pos) const; | |||||
/** | |||||
* Write a byte and advance the current position. | |||||
*/ | |||||
void Write(u8 byte); | |||||
/** | |||||
* Reads at the current position and advances the position. | |||||
*/ | |||||
u8 Read() const; | |||||
/** | |||||
* Serialization function which allows serializing both compound types and serialization primitives. | |||||
*/ | |||||
template<typename T> | |||||
void Serialize(const T& object) | |||||
{ | |||||
typename std::conditional<std::is_base_of<ISerializable, T>::value, SerializeCompoundFunctor, SerializePrimitiveFunctor>::type serializer{*this}; | |||||
serializer(object); | |||||
} | |||||
/** | |||||
* Serialize an integer, std::vector, std::array, CStr, CStrW or a SerializableJSValue. | |||||
* Note that we can serialize integers in a general way and catch all other types using @ref SerializeNonInteger. | |||||
*/ | |||||
template<typename T> | |||||
void SerializePrimitive(const T& object) | |||||
{ | |||||
for (u32 i = 0; i < sizeof(T); ++i) | |||||
{ | |||||
Write(object >> i * 8 & 0xff); | |||||
} | |||||
} | |||||
template<typename T, std::size_t Size> | |||||
void SerializePrimitive(const std::array<T, Size>& object) | |||||
{ | |||||
static_assert(object.size() < std::numeric_limits<u32>::max(), "size of std::array needs to be smaller than u32 max in order to be serialized."); | |||||
SerializePrimitive((u32)object.size()); | |||||
for (u32 i = 0; i < object.size(); ++i) | |||||
Not Done Inline ActionsSame here. leper: Same here. | |||||
{ | |||||
Serialize(object[i]); | |||||
} | |||||
} | |||||
template<typename T> | |||||
void SerializePrimitive(const std::vector<T>& object) | |||||
{ | |||||
ENSURE(object.size() < std::numeric_limits<u32>::max()); | |||||
SerializePrimitive((u32)object.size()); | |||||
for (u32 i = 0; i < object.size(); ++i) | |||||
{ | |||||
Serialize(object[i]); | |||||
} | |||||
} | |||||
/** | |||||
* Allows deserializing both compound types and serialization primitives. | |||||
*/ | |||||
template<typename T> | |||||
void Deserialize(T& object) const | |||||
{ | |||||
typename std::conditional<std::is_base_of<ISerializable, T>::value, DeserializeCompoundFunctor, DeserializePrimitiveFunctor>::type deserializer{*this}; | |||||
deserializer(object); | |||||
} | |||||
template<typename T> | |||||
void DeserializePrimitive(T& object) const | |||||
{ | |||||
object = T(0); | |||||
for (u32 i = 0; i < sizeof(T); ++i) | |||||
{ | |||||
object |= (T)Read() << i * 8; | |||||
} | |||||
} | |||||
template<typename T, std::size_t Size> | |||||
void DeserializePrimitive(std::array<T, Size>& object) const | |||||
{ | |||||
u32 size; | |||||
DeserializePrimitive(size); | |||||
ENSURE(size == object.size()); | |||||
for (u32 i = 0; i < object.size(); ++i) | |||||
{ | |||||
Deserialize(object[i]); | |||||
} | |||||
} | |||||
template<typename T> | |||||
void DeserializePrimitive(std::vector<T>& object) const | |||||
{ | |||||
u32 size; | |||||
DeserializePrimitive(size); | |||||
object.resize(size); | |||||
for (u32 i = 0; i < object.size(); ++i) | |||||
{ | |||||
Deserialize(object[i]); | |||||
} | |||||
} | |||||
/** | |||||
* Serializes an "ISerializable" object. This allows us to select the right function to use at compile time in Serialize (this or SerializePrimitiveFunctor). | |||||
*/ | |||||
struct SerializeCompoundFunctor | |||||
{ | |||||
template<typename T> | |||||
void operator()(const T& object) const | |||||
{ | |||||
static_assert(std::is_base_of<ISerializable, T>::value, "SerializeCompoundFunctor expects a subclass of ISerializable."); | |||||
SerializeFunctor serializer{m_Buffer}; | |||||
object.RegisterSerializable(serializer); | |||||
} | |||||
CNetMessageBuffer& m_Buffer; | |||||
}; | |||||
struct SerializePrimitiveFunctor | |||||
{ | |||||
template<typename T> | |||||
void operator()(const T& object) const | |||||
{ | |||||
m_Buffer.SerializePrimitive(object); | |||||
} | |||||
CNetMessageBuffer& m_Buffer; | |||||
}; | |||||
/** | |||||
* Functor which should be used for the call to RegisterSerializable on compound objects. | |||||
*/ | |||||
struct SerializeFunctor | |||||
{ | |||||
template<typename T> | |||||
void operator()(const T& object) const | |||||
{ | |||||
m_Buffer.Serialize(object); | |||||
} | |||||
/** | |||||
* For ease of use, also provide a variadic version. | |||||
*/ | |||||
template<typename T, typename... Args> | |||||
void operator()(const T& object, const Args&... args) const | |||||
{ | |||||
(*this)(object); | |||||
(*this)(args...); | |||||
} | |||||
CNetMessageBuffer& m_Buffer; | |||||
}; | |||||
struct DeserializeCompoundFunctor | |||||
{ | |||||
template<typename T> | |||||
void operator()(T& object) const | |||||
{ | |||||
static_assert(std::is_base_of<ISerializable, T>::value, "DeserializeCompoundFunctor expects a subclass of ISerializable."); | |||||
DeserializeFunctor deserializer{m_Buffer}; | |||||
object.RegisterSerializable(deserializer); | |||||
} | |||||
const CNetMessageBuffer& m_Buffer; | |||||
}; | |||||
struct DeserializePrimitiveFunctor | |||||
{ | |||||
template<typename T> | |||||
void operator()(T& object) const | |||||
{ | |||||
m_Buffer.DeserializePrimitive(object); | |||||
} | |||||
const CNetMessageBuffer& m_Buffer; | |||||
}; | |||||
struct DeserializeFunctor | |||||
{ | |||||
template<typename T> | |||||
void operator()(T& object) const | |||||
{ | |||||
m_Buffer.Deserialize(object); | |||||
} | |||||
template<typename T, typename... Args> | |||||
void operator()(T& object, Args&... args) const | |||||
{ | |||||
(*this)(object); | |||||
(*this)(args...); | |||||
} | |||||
const CNetMessageBuffer& m_Buffer; | |||||
}; | |||||
static const u32 HEADER_SIZE = 5; | |||||
std::vector<u8> m_Buffer; | |||||
Done Inline ActionsI do like the comments what #if this closes, especially if there is most of the file in-between. leper: I do like the comments what #if this closes, especially if there is most of the file in-between. | |||||
mutable u32 m_Position; | |||||
NetMessageType m_Type; | |||||
}; | |||||
template<> | |||||
void CNetMessageBuffer::Serialize(const CStr& object); | |||||
/** | |||||
* CStrW always serializes to u16. | |||||
*/ | |||||
template<> | |||||
void CNetMessageBuffer::Serialize(const CStrW& object); | |||||
template<> | |||||
void CNetMessageBuffer::Serialize(const SerializableJSValue& object); | |||||
template<> | |||||
void CNetMessageBuffer::Deserialize(CStr& object) const; | |||||
template<> | |||||
void CNetMessageBuffer::Deserialize(CStrW& object) const; | |||||
template<> | |||||
void CNetMessageBuffer::Deserialize(SerializableJSValue& object) const; | |||||
#endif // NETMESSAGEBUFFER_H |
You might want to update that for the new filename.