Skip to content

Instantly share code, notes, and snippets.

@JonathanCline
Created August 12, 2023 05:43
Show Gist options
  • Save JonathanCline/3bedb57f089d5c49c6df4fe563843dc8 to your computer and use it in GitHub Desktop.
Save JonathanCline/3bedb57f089d5c49c6df4fe563843dc8 to your computer and use it in GitHub Desktop.
namespace sw::net
{
std::vector<std::byte> Message::serialize() const
{
auto _out = std::vector<std::byte>(2);
// Write message type first
{
auto _outIt = _out.begin();
const auto _messageType = this->type();
_outIt = write_bytes(_outIt, jc::to_underlying(_messageType));
};
// Write message DATA after the type indicator
const auto _bytesWritten =
std::visit([&_out](const auto& _message) -> size_t
{
return _message.serialize(_out);
}, this->vt_);
return _out;
};
template <typename MessageT>
inline std::optional<Message> deserialize_message_helper(std::span<const std::byte> _data)
{
if (auto _messageData = Message_Ping::deserialize(_data); _messageData)
{
return Message(std::move(*_messageData));
}
else
{
return std::nullopt;
};
};
std::optional<Message> Message::deserialize(std::span<const std::byte> _data)
{
if (_data.size() < sizeof(MessageType))
{
return std::nullopt;
};
auto _inIt = _data.begin();
// Determine message type
auto _rawMessageType = std::underlying_type_t<MessageType>{};
_inIt = read_bytes(_inIt, _rawMessageType);
// Where the raw message byte data is stored
const auto _messageDataBytes = std::span(_inIt, _data.end());
// MESSAGE_TYPE_UPDATE_TAG
// Update the below switch with all possible enumerator values and their
// corresponding message data types.
switch (MessageType(_rawMessageType))
{
case MessageType::ping:
return deserialize_message_helper<Message_Ping>(_messageDataBytes);
default:
// Unrecognized message type.
return std::nullopt;
};
};
};
namespace sw::net
{
size_t Message_Ping::serialize(std::vector<std::byte>& _out) const
{
const auto _oldSize = _out.size();
_out.resize(_oldSize + sizeof(*this));
auto _outIt = _out.begin() + _oldSize;
_outIt = write_bytes(_outIt, this->ping_time.time_since_epoch().count());
_outIt = write_bytes(_outIt, this->pong_time.time_since_epoch().count());
return _out.size() - _oldSize;
};
std::optional<Message_Ping> Message_Ping::deserialize(std::span<const std::byte> _data)
{
if (_data.size() < sizeof(Message_Ping))
{
return std::nullopt;
};
Message_Ping _out{};
auto _pingTimeSinceEpoch = Message_Ping::time_point::rep{};
auto _pongTimeSinceEpoch = Message_Ping::time_point::rep{};
auto _inIt = _data.begin();
_inIt = read_bytes(_inIt, _pingTimeSinceEpoch);
_inIt = read_bytes(_inIt, _pongTimeSinceEpoch);
_out.ping_time = time_point{ time_point::duration{_pingTimeSinceEpoch} };
_out.pong_time = time_point{ time_point::duration{_pongTimeSinceEpoch} };
return _out;
};
};
namespace sw::net
{
/**
* @brief Enumerates the types of messages that can be transmitted.
*
* Find tag MESSAGE_TYPE_UPDATE_TAG and update whatever you find if you
* modify the below enumerator.
*/
enum class MessageType : uint16_t
{
/**
* @brief Performs a ping/pong action to gauge latency
*/
ping = 0,
};
/**
* @brief Message data structure for the "ping" message.
*/
struct Message_Ping
{
using time_point = std::chrono::utc_clock::time_point;
/**
* @brief Serializes just the message DATA into a byte array.
* @param _out Vector of bytes to write serialized data into.
* @return Number of bytes written to _out.
*/
size_t serialize(std::vector<std::byte>& _out) const;
/**
* @brief Deserializes just the message DATA from some raw byte data.
* @param _data Raw byte data, ideally containing our message.
* @return Deserialized message DATA on success, nullopt otherwise.
*/
static std::optional<Message_Ping> deserialize(std::span<const std::byte> _data);
/**
* @brief Time the ping was initiated at.
*/
time_point ping_time;
/**
* @brief Time the pong was initiated at.
*/
time_point pong_time;
};
/**
* @brief Data structure for the actual message (data) carried by a datagram.
*/
class Message
{
private:
/**
* @brief
* MESSAGE_TYPE_UPDATE_TAG Add the corresponding message data type to the variant.
*/
using vt = std::variant
<
Message_Ping
>;
public:
MessageType type() const noexcept
{
return static_cast<MessageType>(this->vt_.index());
};
template <MessageType T>
auto& get()
{
return std::get<static_cast<size_t>(T)>(this->vt_);
};
template <MessageType T>
const auto& get() const
{
return std::get<static_cast<size_t>(T)>(this->vt_);
};
/**
* @brief Serializes the message into raw bytes.
* @return Raw byte array.
*/
std::vector<std::byte> serialize() const;
/**
* @brief Attempts to deserialize a message from raw bytes.
* @param _data Raw byte data containing a message.
* @return Message on good deserialize, otherwise nullopt.
*/
static std::optional<Message> deserialize(std::span<const std::byte> _data);
Message() :
vt_(Message_Ping{})
{};
template <jc::cx_element_of<vt> T>
Message(const T& _message) :
vt_(_message)
{};
template <jc::cx_element_of<vt> T>
Message(T&& _message) :
vt_(std::move(_message))
{};
private:
/**
* @brief Storage for the actual message data structure.
*/
vt vt_;
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment