Refactor NetworkMessage and NetworkStream to handle errors gracefully, including in Release builds
This commit is contained in:
@@ -42,7 +42,7 @@ void SendPacketToPeer(ENetPeer* peer, const NetworkChannelType channelType, cons
|
||||
// Tho, we cannot use it, because we're releasing the message right after the send - and the packet might not
|
||||
// be sent, yet. To avoid data corruption, we're just using the copy method. We might fix that later, but I'll take
|
||||
// the smaller risk.
|
||||
ENetPacket* packet = enet_packet_create(message.Buffer, message.Length, flag);
|
||||
ENetPacket* packet = enet_packet_create(message.Buffer, message.Position, flag);
|
||||
|
||||
// And send it!
|
||||
enet_peer_send(peer, 0, packet);
|
||||
@@ -195,7 +195,7 @@ bool ENetDriver::PopEvent(NetworkEvent& eventPtr)
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
eventPtr.EventType = NetworkEventType::Message;
|
||||
eventPtr.Message = _networkHost->CreateMessage();
|
||||
eventPtr.Message.Length = event.packet->dataLength;
|
||||
eventPtr.Message.BufferSize = event.packet->dataLength;
|
||||
Platform::MemoryCopy(eventPtr.Message.Buffer, event.packet->data, event.packet->dataLength);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -134,8 +134,8 @@ void NetworkLagDriver::SendMessage(const NetworkChannelType channelType, const N
|
||||
msg.Lag = (double)Lag;
|
||||
msg.ChannelType = channelType;
|
||||
msg.Type = 0;
|
||||
msg.MessageData.Set(message.Buffer, message.Length);
|
||||
msg.MessageLength = message.Length;
|
||||
msg.MessageData.Set(message.Buffer, message.Position);
|
||||
msg.MessageLength = message.Position;
|
||||
}
|
||||
|
||||
void NetworkLagDriver::SendMessage(NetworkChannelType channelType, const NetworkMessage& message, NetworkConnection target)
|
||||
@@ -151,8 +151,8 @@ void NetworkLagDriver::SendMessage(NetworkChannelType channelType, const Network
|
||||
msg.ChannelType = channelType;
|
||||
msg.Type = 1;
|
||||
msg.Target = target;
|
||||
msg.MessageData.Set(message.Buffer, message.Length);
|
||||
msg.MessageLength = message.Length;
|
||||
msg.MessageData.Set(message.Buffer, message.Position);
|
||||
msg.MessageLength = message.Position;
|
||||
}
|
||||
|
||||
void NetworkLagDriver::SendMessage(const NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets)
|
||||
@@ -168,8 +168,8 @@ void NetworkLagDriver::SendMessage(const NetworkChannelType channelType, const N
|
||||
msg.ChannelType = channelType;
|
||||
msg.Type = 2;
|
||||
msg.Targets = targets;
|
||||
msg.MessageData.Set(message.Buffer, message.Length);
|
||||
msg.MessageLength = message.Length;
|
||||
msg.MessageData.Set(message.Buffer, message.Position);
|
||||
msg.MessageLength = message.Position;
|
||||
}
|
||||
|
||||
NetworkDriverStats NetworkLagDriver::GetStats()
|
||||
@@ -203,7 +203,7 @@ void NetworkLagDriver::OnUpdate()
|
||||
// Use this helper message as a container to send the stored data and length to the ENet driver
|
||||
NetworkMessage message;
|
||||
message.Buffer = msg.MessageData.Get();
|
||||
message.Length = msg.MessageLength;
|
||||
message.BufferSize = msg.MessageLength;
|
||||
|
||||
switch (msg.Type)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#endif
|
||||
|
||||
// Internal version number of networking implementation. Updated once engine changes serialization or connection rules.
|
||||
#define NETWORK_PROTOCOL_VERSION 5
|
||||
#define NETWORK_PROTOCOL_VERSION 6
|
||||
|
||||
// Enables encoding object ids and typenames via uint32 keys rather than full data send.
|
||||
#define USE_NETWORK_KEYS 1
|
||||
|
||||
@@ -819,14 +819,18 @@ void NetworkManagerService::Update()
|
||||
if (id < (uint8)NetworkMessageIDs::MAX)
|
||||
{
|
||||
MessageHandlers[id](event, client, peer);
|
||||
if (EnumHasAnyFlags(event.Message.Flags, NetworkMessageFlags::HasError))
|
||||
{
|
||||
LOG(Warning, "Error occurred while processing message id={0} from connection {1}", id, event.Sender.ConnectionId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Unknown message id={0} from connection {1}", id, event.Sender.ConnectionId);
|
||||
}
|
||||
}
|
||||
peer->RecycleMessage(event.Message);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
eventIsValid = false;
|
||||
break;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using FlaxEngine.Assertions;
|
||||
|
||||
namespace FlaxEngine.Networking
|
||||
{
|
||||
@@ -12,26 +10,30 @@ namespace FlaxEngine.Networking
|
||||
/// Writes raw bytes into the message.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes that will be written.</param>
|
||||
/// <param name="length">The amount of bytes to write from the bytes pointer.</param>
|
||||
/// <param name="length">The amount of bytes to write from the pointer.</param>
|
||||
public void WriteBytes(byte* bytes, int length)
|
||||
{
|
||||
Assert.IsTrue(Position + length <= BufferSize, $"Could not write data of length {length} into message with id={MessageId}! Current write position={Position}");
|
||||
if (Position + length > BufferSize)
|
||||
{
|
||||
Flags |= NetworkMessageFlags.HasError;
|
||||
return;
|
||||
}
|
||||
Utils.MemoryCopy(new IntPtr(Buffer + Position), new IntPtr(bytes), (ulong)length);
|
||||
Position += (uint)length;
|
||||
Length = Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads raw bytes from the message into the given byte array.
|
||||
/// </summary>
|
||||
/// <param name="buffer">
|
||||
/// The buffer pointer that will be used to store the bytes.
|
||||
/// Should be of the same length as length or longer.
|
||||
/// </param>
|
||||
/// <param name="buffer">The buffer pointer that will be used to store the bytes. Should be of the same length as length or longer.</param>
|
||||
/// <param name="length">The minimal amount of bytes that the buffer contains.</param>
|
||||
public void ReadBytes(byte* buffer, int length)
|
||||
{
|
||||
Assert.IsTrue(Position + length <= Length, $"Could not read data of length {length} from message with id={MessageId} and size of {Length}B! Current read position={Position}");
|
||||
if (Position + length > BufferSize)
|
||||
{
|
||||
Flags |= NetworkMessageFlags.HasError;
|
||||
return;
|
||||
}
|
||||
Utils.MemoryCopy(new IntPtr(buffer), new IntPtr(Buffer + Position), (ulong)length);
|
||||
Position += (uint)length;
|
||||
}
|
||||
@@ -52,10 +54,7 @@ namespace FlaxEngine.Networking
|
||||
/// <summary>
|
||||
/// Reads raw bytes from the message into the given byte array.
|
||||
/// </summary>
|
||||
/// <param name="buffer">
|
||||
/// The buffer that will be used to store the bytes.
|
||||
/// Should be of the same length as length or longer.
|
||||
/// </param>
|
||||
/// <param name="buffer">The buffer that will be used to store the bytes. Should be of the same length as length or longer.</param>
|
||||
/// <param name="length">The minimal amount of bytes that the buffer contains.</param>
|
||||
public void ReadBytes(byte[] buffer, int length)
|
||||
{
|
||||
@@ -251,13 +250,9 @@ namespace FlaxEngine.Networking
|
||||
public void WriteString(string value)
|
||||
{
|
||||
// Note: Make sure that this is consistent with the C++ message API!
|
||||
|
||||
var data = Encoding.Unicode.GetBytes(value);
|
||||
var dataLength = data.Length;
|
||||
var stringLength = value.Length;
|
||||
|
||||
WriteUInt16((ushort)stringLength); // TODO: Use 1-byte length when possible
|
||||
WriteBytes(data, dataLength);
|
||||
WriteUInt16((ushort)value.Length); // TODO: Use 1-byte length when possible
|
||||
fixed (char* ptr = value)
|
||||
WriteBytes((byte*)ptr, value.Length * 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -266,13 +261,24 @@ namespace FlaxEngine.Networking
|
||||
public string ReadString()
|
||||
{
|
||||
// Note: Make sure that this is consistent with the C++ message API!
|
||||
|
||||
var stringLength = ReadUInt16(); // In chars
|
||||
var dataLength = stringLength * sizeof(char); // In bytes
|
||||
var bytes = stackalloc char[stringLength];
|
||||
|
||||
ReadBytes((byte*)bytes, dataLength);
|
||||
return new string(bytes, 0, stringLength);
|
||||
var stringLength = ReadUInt16();
|
||||
if (stringLength < 200)
|
||||
{
|
||||
var bytes = stackalloc char[stringLength];
|
||||
ReadBytes((byte*)bytes, stringLength * sizeof(char));
|
||||
if ((Flags & NetworkMessageFlags.HasError) != 0)
|
||||
return null;
|
||||
return new string(bytes, 0, stringLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
var bytes = new char[stringLength];
|
||||
fixed (char* bytesPtr = bytes)
|
||||
ReadBytes((byte*)bytesPtr, stringLength * sizeof(char));
|
||||
if ((Flags & NetworkMessageFlags.HasError) != 0)
|
||||
return null;
|
||||
return new string(bytes, 0, stringLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -352,10 +358,7 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public void WriteQuaternion(Quaternion value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
WriteSingle(value.W);
|
||||
WriteBytes((byte*)&value, sizeof(Quaternion));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -363,7 +366,9 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public Quaternion ReadQuaternion()
|
||||
{
|
||||
return new Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||
Quaternion result = Quaternion.Identity;
|
||||
ReadBytes((byte*)&result, sizeof(Quaternion));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,41 +6,55 @@
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Math/Vector4.h"
|
||||
#include "Engine/Core/Math/Quaternion.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
/// <summary>
|
||||
/// Message flags.
|
||||
/// </summary>
|
||||
API_ENUM(Attributes="Flags") enum class NetworkMessageFlags : uint32
|
||||
{
|
||||
// No flags.
|
||||
None = 0,
|
||||
// Indicates an error occurred during reading/writing to the message buffer.
|
||||
HasError = 1,
|
||||
};
|
||||
|
||||
DECLARE_ENUM_OPERATORS(NetworkMessageFlags);
|
||||
|
||||
/// <summary>
|
||||
/// Network message structure. Provides raw data writing and reading to the message buffer.
|
||||
/// </summary>
|
||||
API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkMessage
|
||||
API_STRUCT(Namespace="FlaxEngine.Networking", NoDefault) struct FLAXENGINE_API NetworkMessage
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkMessage);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The raw message buffer.
|
||||
/// </summary>
|
||||
API_FIELD() uint8* Buffer = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// The unique, internal message identifier.
|
||||
/// </summary>
|
||||
API_FIELD() uint32 MessageId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The size in bytes of the buffer that this message has.
|
||||
/// </summary>
|
||||
API_FIELD() uint32 BufferSize = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The length in bytes of this message.
|
||||
/// </summary>
|
||||
API_FIELD() uint32 Length = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The position in bytes in buffer where the next read/write will occur.
|
||||
/// </summary>
|
||||
API_FIELD() uint32 Position = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The unique, internal message identifier.
|
||||
/// </summary>
|
||||
API_FIELD() uint32 MessageId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Set of flags that describe the message state.
|
||||
/// </summary>
|
||||
API_FIELD() NetworkMessageFlags Flags = NetworkMessageFlags::None;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes default values of the <seealso cref="NetworkMessage"/> structure.
|
||||
@@ -50,12 +64,10 @@ public:
|
||||
/// <summary>
|
||||
/// Initializes values of the <seealso cref="NetworkMessage"/> structure.
|
||||
/// </summary>
|
||||
NetworkMessage(uint8* buffer, uint32 messageId, uint32 bufferSize, uint32 length, uint32 position)
|
||||
NetworkMessage(uint8* buffer, uint32 bufferSize, uint32 messageId = 0)
|
||||
: Buffer(buffer)
|
||||
, MessageId(messageId)
|
||||
, BufferSize(bufferSize)
|
||||
, Length(length)
|
||||
, Position(position)
|
||||
, MessageId(messageId)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -66,58 +78,66 @@ public:
|
||||
/// Writes raw bytes into the message.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes that will be written.</param>
|
||||
/// <param name="numBytes">The amount of bytes to write from the bytes pointer.</param>
|
||||
FORCE_INLINE void WriteBytes(const uint8* bytes, const int32 numBytes)
|
||||
/// <param name="length">The amount of bytes to write from the pointer.</param>
|
||||
void WriteBytes(const void* bytes, const int32 length)
|
||||
{
|
||||
ASSERT(Position + numBytes <= BufferSize);
|
||||
Platform::MemoryCopy(Buffer + Position, bytes, numBytes);
|
||||
Position += numBytes;
|
||||
Length = Position;
|
||||
if (Position + length > BufferSize)
|
||||
{
|
||||
Flags |= NetworkMessageFlags::HasError;
|
||||
return;
|
||||
}
|
||||
Platform::MemoryCopy(Buffer + Position, bytes, length);
|
||||
Position += length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads raw bytes from the message into the given byte array.
|
||||
/// </summary>
|
||||
/// <param name="bytes">
|
||||
/// The buffer pointer that will be used to store the bytes.
|
||||
/// Should be of the same length as length or longer.
|
||||
/// </param>
|
||||
/// <param name="numBytes">The minimal amount of bytes that the buffer contains.</param>
|
||||
FORCE_INLINE void ReadBytes(uint8* bytes, const int32 numBytes)
|
||||
/// <param name="bytes">The buffer pointer that will be used to store the bytes. Should be of the same length as length or longer.</param>
|
||||
/// <param name="length">The minimal amount of bytes that the buffer contains.</param>
|
||||
void ReadBytes(void* bytes, const int32 length)
|
||||
{
|
||||
ASSERT(Position + numBytes <= BufferSize);
|
||||
Platform::MemoryCopy(bytes, Buffer + Position, numBytes);
|
||||
Position += numBytes;
|
||||
if (Position + length > BufferSize)
|
||||
{
|
||||
Flags |= NetworkMessageFlags::HasError;
|
||||
return;
|
||||
}
|
||||
Platform::MemoryCopy(bytes, Buffer + Position, length);
|
||||
Position += length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips bytes from the message.
|
||||
/// </summary>
|
||||
/// <param name="numBytes">Amount of bytes to skip.</param>
|
||||
/// <param name="length">Amount of bytes to skip.</param>
|
||||
/// <returns>Pointer to skipped data beginning.</returns>
|
||||
FORCE_INLINE void* SkipBytes(const int32 numBytes)
|
||||
void* SkipBytes(const int32 length)
|
||||
{
|
||||
ASSERT(Position + numBytes <= BufferSize);
|
||||
if (Position + length > BufferSize)
|
||||
{
|
||||
Flags |= NetworkMessageFlags::HasError;
|
||||
return nullptr;
|
||||
}
|
||||
byte* result = Buffer + Position;
|
||||
Position += numBytes;
|
||||
Position += length;
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
FORCE_INLINE void WriteStructure(const T& data)
|
||||
{
|
||||
WriteBytes((const uint8*)&data, sizeof(data));
|
||||
WriteBytes(&data, sizeof(data));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
FORCE_INLINE void ReadStructure(const T& data)
|
||||
{
|
||||
ReadBytes((uint8*)&data, sizeof(data));
|
||||
ReadBytes((T*)&data, sizeof(data));
|
||||
}
|
||||
|
||||
#define DECL_READWRITE(type, name) \
|
||||
FORCE_INLINE void Write##name(type value) { WriteBytes(reinterpret_cast<const uint8*>(&value), sizeof(type)); } \
|
||||
FORCE_INLINE type Read##name() { type value = 0; ReadBytes(reinterpret_cast<uint8*>(&value), sizeof(type)); return value; }
|
||||
void Write##name(type value) { WriteBytes(&value, sizeof(type)); } \
|
||||
type Read##name() { type value = 0; ReadBytes(&value, sizeof(type)); return value; }
|
||||
DECL_READWRITE(int8, Int8)
|
||||
DECL_READWRITE(uint8, UInt8)
|
||||
DECL_READWRITE(int16, Int16)
|
||||
@@ -134,7 +154,7 @@ public:
|
||||
/// <summary>
|
||||
/// Writes data of type Vector2 into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteVector2(const Vector2& value)
|
||||
void WriteVector2(const Vector2& value)
|
||||
{
|
||||
WriteSingle((float)value.X);
|
||||
WriteSingle((float)value.Y);
|
||||
@@ -143,7 +163,7 @@ public:
|
||||
/// <summary>
|
||||
/// Reads and returns data of type Vector2 from the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE Vector2 ReadVector2()
|
||||
Vector2 ReadVector2()
|
||||
{
|
||||
return Vector2(ReadSingle(), ReadSingle());
|
||||
}
|
||||
@@ -151,7 +171,7 @@ public:
|
||||
/// <summary>
|
||||
/// Writes data of type Vector3 into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteVector3(const Vector3& value)
|
||||
void WriteVector3(const Vector3& value)
|
||||
{
|
||||
WriteSingle((float)value.X);
|
||||
WriteSingle((float)value.Y);
|
||||
@@ -161,7 +181,7 @@ public:
|
||||
/// <summary>
|
||||
/// Reads and returns data of type Vector3 from the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE Vector3 ReadVector3()
|
||||
Vector3 ReadVector3()
|
||||
{
|
||||
return Vector3(ReadSingle(), ReadSingle(), ReadSingle());
|
||||
}
|
||||
@@ -169,7 +189,7 @@ public:
|
||||
/// <summary>
|
||||
/// Writes data of type Vector4 into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteVector4(const Vector4& value)
|
||||
void WriteVector4(const Vector4& value)
|
||||
{
|
||||
WriteSingle((float)value.X);
|
||||
WriteSingle((float)value.Y);
|
||||
@@ -180,7 +200,7 @@ public:
|
||||
/// <summary>
|
||||
/// Reads and returns data of type Vector4 from the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE Vector4 ReadVector4()
|
||||
Vector4 ReadVector4()
|
||||
{
|
||||
return Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||
}
|
||||
@@ -188,62 +208,73 @@ public:
|
||||
/// <summary>
|
||||
/// Writes data of type Quaternion into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteQuaternion(const Quaternion& value)
|
||||
void WriteQuaternion(const Quaternion& value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
WriteSingle(value.W);
|
||||
WriteBytes(&value, sizeof(Quaternion));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type Quaternion from the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE Quaternion ReadQuaternion()
|
||||
Quaternion ReadQuaternion()
|
||||
{
|
||||
return Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||
Quaternion result = Quaternion::Identity;
|
||||
ReadBytes(&result, sizeof(Quaternion));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type String into the message. UTF-16 encoded.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteString(const StringView& value)
|
||||
void WriteString(const StringView& value)
|
||||
{
|
||||
WriteUInt16(value.Length()); // TODO: Use 1-byte length when possible
|
||||
WriteBytes((const uint8*)value.Get(), value.Length() * sizeof(Char));
|
||||
WriteBytes(value.Get(), value.Length() * sizeof(Char));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type String into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteStringAnsi(const StringAnsiView& value)
|
||||
void WriteStringAnsi(const StringAnsiView& value)
|
||||
{
|
||||
WriteUInt16(value.Length()); // TODO: Use 1-byte length when possible
|
||||
WriteBytes((const uint8*)value.Get(), value.Length());
|
||||
WriteBytes(value.Get(), value.Length());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type String from the message. UTF-16 encoded. Data valid within message lifetime.
|
||||
/// </summary>
|
||||
FORCE_INLINE StringView ReadString()
|
||||
StringView ReadString()
|
||||
{
|
||||
const uint16 length = ReadUInt16();
|
||||
return StringView(length ? (const Char*)SkipBytes(length * 2) : nullptr, length);
|
||||
if (length)
|
||||
{
|
||||
auto str = SkipBytes(length * 2);
|
||||
if (str)
|
||||
return StringView((const Char*)str, length);
|
||||
}
|
||||
return StringView::Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type String from the message. ANSI encoded. Data valid within message lifetime.
|
||||
/// </summary>
|
||||
FORCE_INLINE StringAnsiView ReadStringAnsi()
|
||||
StringAnsiView ReadStringAnsi()
|
||||
{
|
||||
const uint16 length = ReadUInt16();
|
||||
return StringAnsiView(length ? (const char*)SkipBytes(length) : nullptr, length);
|
||||
if (length)
|
||||
{
|
||||
auto str = SkipBytes(length);
|
||||
if (str)
|
||||
return StringAnsiView((const char*)str, length);
|
||||
}
|
||||
return StringAnsiView::Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type Guid into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteGuid(const Guid& value)
|
||||
void WriteGuid(const Guid& value)
|
||||
{
|
||||
WriteBytes((const uint8*)&value, sizeof(Guid));
|
||||
}
|
||||
@@ -251,9 +282,9 @@ public:
|
||||
/// <summary>
|
||||
/// Reads and returns data of type Guid from the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE Guid ReadGuid()
|
||||
Guid ReadGuid()
|
||||
{
|
||||
Guid value;
|
||||
Guid value = Guid::Empty;
|
||||
ReadBytes((uint8*)&value, sizeof(Guid));
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -82,8 +82,9 @@ void NetworkPeer::Shutdown()
|
||||
|
||||
void NetworkPeer::CreateMessageBuffers()
|
||||
{
|
||||
if (MessageBuffer)
|
||||
return;
|
||||
PROFILE_MEM(Networking);
|
||||
ASSERT(MessageBuffer == nullptr);
|
||||
|
||||
const uint32 pageSize = Platform::GetCPUInfo().PageSize;
|
||||
|
||||
@@ -99,10 +100,11 @@ void NetworkPeer::CreateMessageBuffers()
|
||||
|
||||
void NetworkPeer::DisposeMessageBuffers()
|
||||
{
|
||||
ASSERT(MessageBuffer != nullptr);
|
||||
|
||||
Platform::FreePages(MessageBuffer);
|
||||
MessageBuffer = nullptr;
|
||||
if (MessageBuffer)
|
||||
{
|
||||
Platform::FreePages(MessageBuffer);
|
||||
MessageBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkPeer::Listen()
|
||||
@@ -137,17 +139,16 @@ bool NetworkPeer::PopEvent(NetworkEvent& eventRef)
|
||||
|
||||
NetworkMessage NetworkPeer::CreateMessage()
|
||||
{
|
||||
CHECK_RETURN(MessagePool.HasItems(), NetworkMessage());
|
||||
const uint32 messageId = MessagePool.Pop();
|
||||
uint8* messageBuffer = GetMessageBuffer(messageId);
|
||||
return NetworkMessage(messageBuffer, messageId, Config.MessageSize, 0, 0);
|
||||
return NetworkMessage(messageBuffer, Config.MessageSize, messageId);
|
||||
}
|
||||
|
||||
void NetworkPeer::RecycleMessage(const NetworkMessage& message)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
#ifdef BUILD_DEBUG
|
||||
ASSERT(MessagePool.Contains(message.MessageId) == false);
|
||||
#endif
|
||||
CHECK(message.IsValid());
|
||||
ASSERT_LOW_LAYER(MessagePool.Contains(message.MessageId) == false);
|
||||
|
||||
// Return the message id
|
||||
MessagePool.Push(message.MessageId);
|
||||
@@ -160,13 +161,14 @@ NetworkMessage NetworkPeer::BeginSendMessage()
|
||||
|
||||
void NetworkPeer::AbortSendMessage(const NetworkMessage& message)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
CHECK(message.IsValid());
|
||||
RecycleMessage(message);
|
||||
}
|
||||
|
||||
bool NetworkPeer::EndSendMessage(const NetworkChannelType channelType, const NetworkMessage& message)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
CHECK_RETURN(message.IsValid(), true);
|
||||
CHECK_RETURN(EnumHasNoneFlags(message.Flags, NetworkMessageFlags::HasError), true);
|
||||
|
||||
NetworkDriver->SendMessage(channelType, message);
|
||||
|
||||
@@ -176,7 +178,8 @@ bool NetworkPeer::EndSendMessage(const NetworkChannelType channelType, const Net
|
||||
|
||||
bool NetworkPeer::EndSendMessage(const NetworkChannelType channelType, const NetworkMessage& message, const NetworkConnection& target)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
CHECK_RETURN(message.IsValid(), true);
|
||||
CHECK_RETURN(EnumHasNoneFlags(message.Flags, NetworkMessageFlags::HasError), true);
|
||||
|
||||
NetworkDriver->SendMessage(channelType, message, target);
|
||||
|
||||
@@ -186,7 +189,8 @@ bool NetworkPeer::EndSendMessage(const NetworkChannelType channelType, const Net
|
||||
|
||||
bool NetworkPeer::EndSendMessage(const NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection>& targets)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
CHECK_RETURN(message.IsValid(), true);
|
||||
CHECK_RETURN(EnumHasNoneFlags(message.Flags, NetworkMessageFlags::HasError), true);
|
||||
|
||||
NetworkDriver->SendMessage(channelType, message, targets);
|
||||
|
||||
|
||||
@@ -98,10 +98,8 @@ public:
|
||||
/// <param name="channelType">The channel to send the message over.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <remarks>Can be used only by the client!</remarks>
|
||||
/// <remarks>
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
/// <remarks>Do not recycle the message after calling this. This function automatically recycles the message.</remarks>
|
||||
/// <returns>True if failed to send a message, otherwise false.</returns>
|
||||
API_FUNCTION() bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message);
|
||||
|
||||
/// <summary>
|
||||
@@ -111,10 +109,8 @@ public:
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="target">The client connection to send the message to.</param>
|
||||
/// <remarks>Can be used only by the server!</remarks>
|
||||
/// <remarks>
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
/// <remarks>Do not recycle the message after calling this. This function automatically recycles the message.</remarks>
|
||||
/// <returns>True if failed to send a message, otherwise false.</returns>
|
||||
API_FUNCTION() bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const NetworkConnection& target);
|
||||
|
||||
/// <summary>
|
||||
@@ -124,10 +120,8 @@ public:
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="targets">The connections list to send the message to.</param>
|
||||
/// <remarks>Can be used only by the server!</remarks>
|
||||
/// <remarks>
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
/// <remarks>Do not recycle the message after calling this. This function automatically recycles the message.</remarks>
|
||||
/// <returns>True if failed to send a message, otherwise false.</returns>
|
||||
API_FUNCTION() bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
#include "FlaxEngine.Gen.h"
|
||||
#endif
|
||||
|
||||
#define NETWORK_STREAM_SIZE_LIMIT 65535
|
||||
|
||||
#if !BUILD_RELEASE
|
||||
bool NetworkReplicator::EnableLog = false;
|
||||
#include "Engine/Core/Log.h"
|
||||
@@ -541,7 +543,7 @@ void SendInParts(NetworkPeer* peer, NetworkChannelType channel, const byte* data
|
||||
msgDataPayload.PartSize = msgDataSize;
|
||||
msg.WriteStructure(msgDataPayload);
|
||||
msg.WriteBytes(data, msgDataSize);
|
||||
uint32 messageSize = msg.Length;
|
||||
uint32 messageSize = msg.Position;
|
||||
if (toServer)
|
||||
peer->EndSendMessage(channel, msg);
|
||||
else
|
||||
@@ -561,7 +563,7 @@ void SendInParts(NetworkPeer* peer, NetworkChannelType channel, const byte* data
|
||||
msg.WriteStructure(msgDataPart);
|
||||
msg.WriteNetworkId(objectId);
|
||||
msg.WriteBytes(data + msgDataPart.PartStart, msgDataPart.PartSize);
|
||||
messageSize += msg.Length;
|
||||
messageSize += msg.Position;
|
||||
dataStart += msgDataPart.PartSize;
|
||||
if (toServer)
|
||||
peer->EndSendMessage(channel, msg);
|
||||
@@ -718,13 +720,16 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients)
|
||||
const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, true);
|
||||
if (failed)
|
||||
{
|
||||
//NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString());
|
||||
if (stream->HasError())
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to serialize object {} of type {}", item.ToString(), obj->GetType().ToString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
const uint32 size = stream->GetPosition();
|
||||
if (size > MAX_uint16)
|
||||
if (size > NETWORK_STREAM_SIZE_LIMIT)
|
||||
{
|
||||
LOG(Error, "Too much data for object {} replication ({} bytes provided while limit is {}).", item.ToString(), size, MAX_uint16);
|
||||
LOG(Error, "Too much data for object {} replication ({} bytes provided while limit is {}).", item.ToString(), size, NETWORK_STREAM_SIZE_LIMIT);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -788,9 +793,9 @@ void SendRpc(RpcSendItem& e)
|
||||
return;
|
||||
}
|
||||
auto& item = it->Item;
|
||||
if (e.ArgsData.Length() > MAX_uint16)
|
||||
if (e.ArgsData.Length() > NETWORK_STREAM_SIZE_LIMIT)
|
||||
{
|
||||
LOG(Error, "Too much data for object RPC method '{}.{}' on object '{}' ({} bytes provided while limit is {}).", e.Name.First.ToString(), e.Name.Second.ToString(), obj->GetID(), e.ArgsData.Length(), MAX_uint16);
|
||||
LOG(Error, "Too much data for object RPC method '{}.{}' on object '{}' ({} bytes provided while limit is {}).", e.Name.First.ToString(), e.Name.Second.ToString(), obj->GetID(), e.ArgsData.Length(), NETWORK_STREAM_SIZE_LIMIT);
|
||||
return;
|
||||
}
|
||||
const NetworkManagerMode mode = NetworkManager::Mode;
|
||||
@@ -1015,7 +1020,11 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b
|
||||
const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, false);
|
||||
if (failed)
|
||||
{
|
||||
//NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString());
|
||||
if (stream->HasError())
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to deserialize object {} of type {}", item.ToString(), obj->GetType().ToString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.AsNetworkObject)
|
||||
@@ -1049,6 +1058,10 @@ void InvokeObjectRpc(const NetworkRpcInfo* info, byte* data, uint32 dataSize, ui
|
||||
|
||||
// Execute RPC
|
||||
info->Execute(obj, stream, info->Tag);
|
||||
if (stream->HasError())
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to read stream with arguments of RPC for object {}", obj->GetType().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const Guid& prefabId, const NetworkMessageObjectSpawnItem* msgDataItems)
|
||||
@@ -1426,7 +1439,7 @@ bool NetworkReplicator::InvokeSerializer(const ScriptingTypeHandle& typeHandle,
|
||||
// Invoke serializer
|
||||
const byte idx = serialize ? 0 : 1;
|
||||
serializer.Methods[idx](instance, stream, serializer.Tags[idx]);
|
||||
return false;
|
||||
return stream->HasError();
|
||||
}
|
||||
|
||||
void NetworkReplicator::AddObject(ScriptingObject* obj, const ScriptingObject* parent)
|
||||
@@ -1825,6 +1838,11 @@ bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa
|
||||
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
|
||||
if (!info || !obj || NetworkManager::IsOffline())
|
||||
return false;
|
||||
if (argsStream && argsStream->HasError())
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to write stream with RPC arguments '{}::{}'", type.ToString(), name.ToString());
|
||||
return true;
|
||||
}
|
||||
PROFILE_MEM(Networking);
|
||||
ObjectsLock.Lock();
|
||||
auto& rpc = RpcQueue.AddOne();
|
||||
@@ -1832,7 +1850,8 @@ bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa
|
||||
rpc.Name.First = type;
|
||||
rpc.Name.Second = name;
|
||||
rpc.Info = *info;
|
||||
rpc.ArgsData.Copy(Span<byte>(argsStream->GetBuffer(), argsStream->GetPosition()));
|
||||
if (argsStream)
|
||||
rpc.ArgsData.Copy(Span<byte>(argsStream->GetBuffer(), argsStream->GetPosition()));
|
||||
rpc.Targets.Copy(targetIds);
|
||||
ObjectsLock.Unlock();
|
||||
|
||||
|
||||
@@ -126,8 +126,9 @@ void NetworkStream::Initialize(uint32 minCapacity)
|
||||
_allocated = true;
|
||||
}
|
||||
|
||||
// Reset pointer to the start
|
||||
// Reset state
|
||||
_position = _buffer;
|
||||
ReadStream::_hasError = false;
|
||||
}
|
||||
|
||||
void NetworkStream::Initialize(byte* buffer, uint32 length)
|
||||
@@ -137,6 +138,7 @@ void NetworkStream::Initialize(byte* buffer, uint32 length)
|
||||
_position = _buffer = buffer;
|
||||
_length = length;
|
||||
_allocated = false;
|
||||
ReadStream::_hasError = false;
|
||||
}
|
||||
|
||||
void NetworkStream::Read(INetworkSerializable& obj)
|
||||
@@ -196,6 +198,11 @@ void NetworkStream::Write(const Transform& data, bool useDouble)
|
||||
NetworkQuaternion::Write(this, data.Orientation);
|
||||
}
|
||||
|
||||
bool NetworkStream::HasError() const
|
||||
{
|
||||
return ReadStream::_hasError;
|
||||
}
|
||||
|
||||
void NetworkStream::Flush()
|
||||
{
|
||||
// Nothing to do
|
||||
@@ -208,6 +215,7 @@ void NetworkStream::Close()
|
||||
_position = _buffer = nullptr;
|
||||
_length = 0;
|
||||
_allocated = false;
|
||||
ReadStream::_hasError = false;
|
||||
}
|
||||
|
||||
uint32 NetworkStream::GetLength()
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace FlaxEngine.Networking
|
||||
/// Writes raw bytes into the message.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes that will be written.</param>
|
||||
/// <param name="length">The amount of bytes to write from the bytes pointer.</param>
|
||||
/// <param name="length">The amount of bytes to write from the pointer.</param>
|
||||
public void WriteBytes(byte* bytes, int length)
|
||||
{
|
||||
WriteData(new IntPtr(bytes), length);
|
||||
@@ -20,8 +20,7 @@ namespace FlaxEngine.Networking
|
||||
/// <summary>
|
||||
/// Reads raw bytes from the message into the given byte array.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer pointer that will be used to store the bytes. Should be of the same length as length or longer.
|
||||
/// </param>
|
||||
/// <param name="buffer">The buffer pointer that will be used to store the bytes. Should be of the same length as length or longer.</param>
|
||||
/// <param name="length">The minimal amount of bytes that the buffer contains.</param>
|
||||
public void ReadBytes(byte* buffer, int length)
|
||||
{
|
||||
@@ -285,8 +284,12 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public void WriteVector2(Vector2 value)
|
||||
{
|
||||
WriteSingle((float)value.X);
|
||||
WriteSingle((float)value.Y);
|
||||
#if USE_LARGE_WORLDS
|
||||
var tmp = new Float2(value);
|
||||
WriteBytes((byte*)&tmp, sizeof(Float2));
|
||||
#else
|
||||
WriteBytes((byte*)&value, sizeof(Float2));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -302,9 +305,12 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public void WriteVector3(Vector3 value)
|
||||
{
|
||||
WriteSingle((float)value.X);
|
||||
WriteSingle((float)value.Y);
|
||||
WriteSingle((float)value.Z);
|
||||
#if USE_LARGE_WORLDS
|
||||
var tmp = new Vector3(value);
|
||||
WriteBytes((byte*)&tmp, sizeof(Vector3));
|
||||
#else
|
||||
WriteBytes((byte*)&value, sizeof(Vector3));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -320,10 +326,12 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public void WriteVector4(Vector4 value)
|
||||
{
|
||||
WriteSingle((float)value.X);
|
||||
WriteSingle((float)value.Y);
|
||||
WriteSingle((float)value.Z);
|
||||
WriteSingle((float)value.W);
|
||||
#if USE_LARGE_WORLDS
|
||||
var tmp = new Vector4(value);
|
||||
WriteBytes((byte*)&tmp, sizeof(Vector4));
|
||||
#else
|
||||
WriteBytes((byte*)&value, sizeof(Vector4));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -339,8 +347,7 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public void WriteFloat2(Float2 value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteBytes((byte*)&value, sizeof(Float2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -356,9 +363,7 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public void WriteFloat3(Float3 value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
WriteBytes((byte*)&value, sizeof(Float3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -374,10 +379,7 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public void WriteFloat4(Float4 value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
WriteSingle(value.W);
|
||||
WriteBytes((byte*)&value, sizeof(Float4));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -393,10 +395,8 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public void WriteQuaternion(Quaternion value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
WriteSingle(value.W);
|
||||
// TODO: use NetworkQuaternion to match C++
|
||||
WriteBytes((byte*)&value, sizeof(Quaternion));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -404,7 +404,10 @@ namespace FlaxEngine.Networking
|
||||
/// </summary>
|
||||
public Quaternion ReadQuaternion()
|
||||
{
|
||||
return new Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||
// TODO: use NetworkQuaternion to match C++
|
||||
Quaternion result = Quaternion.Identity;
|
||||
ReadBytes((byte*)&result, sizeof(Quaternion));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -83,6 +83,7 @@ public:
|
||||
|
||||
public:
|
||||
// [Stream]
|
||||
bool HasError() const override;
|
||||
void Flush() override;
|
||||
void Close() override;
|
||||
uint32 GetLength() override;
|
||||
|
||||
Reference in New Issue
Block a user