From ba231ed56df89dd229bf3f15873d3fa6db2c7a62 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Sep 2026 11:41:11 +0200 Subject: [PATCH] Refactor `NetworkMessage` and `NetworkStream` to handle errors gracefully, including in Release builds --- .../Engine/Networking/Drivers/ENetDriver.cpp | 4 +- .../Networking/Drivers/NetworkLagDriver.cpp | 14 +- Source/Engine/Networking/NetworkInternal.h | 2 +- Source/Engine/Networking/NetworkManager.cpp | 6 +- Source/Engine/Networking/NetworkMessage.cs | 71 ++++---- Source/Engine/Networking/NetworkMessage.h | 157 +++++++++++------- Source/Engine/Networking/NetworkPeer.cpp | 32 ++-- Source/Engine/Networking/NetworkPeer.h | 18 +- .../Engine/Networking/NetworkReplicator.cpp | 39 +++-- Source/Engine/Networking/NetworkStream.cpp | 10 +- Source/Engine/Networking/NetworkStream.cs | 55 +++--- Source/Engine/Networking/NetworkStream.h | 1 + 12 files changed, 239 insertions(+), 170 deletions(-) diff --git a/Source/Engine/Networking/Drivers/ENetDriver.cpp b/Source/Engine/Networking/Drivers/ENetDriver.cpp index c9c3e16f9..6975dea46 100644 --- a/Source/Engine/Networking/Drivers/ENetDriver.cpp +++ b/Source/Engine/Networking/Drivers/ENetDriver.cpp @@ -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: diff --git a/Source/Engine/Networking/Drivers/NetworkLagDriver.cpp b/Source/Engine/Networking/Drivers/NetworkLagDriver.cpp index daac952d1..4a36ff8e5 100644 --- a/Source/Engine/Networking/Drivers/NetworkLagDriver.cpp +++ b/Source/Engine/Networking/Drivers/NetworkLagDriver.cpp @@ -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& 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) { diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index 2aade3811..22f2f370d 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -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 diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index 36ff94c5c..35254f06e 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -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; diff --git a/Source/Engine/Networking/NetworkMessage.cs b/Source/Engine/Networking/NetworkMessage.cs index aaf0d4b85..0a32bf4e1 100644 --- a/Source/Engine/Networking/NetworkMessage.cs +++ b/Source/Engine/Networking/NetworkMessage.cs @@ -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. /// /// The bytes that will be written. - /// The amount of bytes to write from the bytes pointer. + /// The amount of bytes to write from the pointer. 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; } /// /// Reads raw bytes from the message into the given byte array. /// - /// - /// The buffer pointer that will be used to store the bytes. - /// Should be of the same length as length or longer. - /// + /// The buffer pointer that will be used to store the bytes. Should be of the same length as length or longer. /// The minimal amount of bytes that the buffer contains. 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 /// /// Reads raw bytes from the message into the given byte array. /// - /// - /// The buffer that will be used to store the bytes. - /// Should be of the same length as length or longer. - /// + /// The buffer that will be used to store the bytes. Should be of the same length as length or longer. /// The minimal amount of bytes that the buffer contains. 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); } /// @@ -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); + } } /// @@ -352,10 +358,7 @@ namespace FlaxEngine.Networking /// public void WriteQuaternion(Quaternion value) { - WriteSingle(value.X); - WriteSingle(value.Y); - WriteSingle(value.Z); - WriteSingle(value.W); + WriteBytes((byte*)&value, sizeof(Quaternion)); } /// @@ -363,7 +366,9 @@ namespace FlaxEngine.Networking /// public Quaternion ReadQuaternion() { - return new Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); + Quaternion result = Quaternion.Identity; + ReadBytes((byte*)&result, sizeof(Quaternion)); + return result; } /// diff --git a/Source/Engine/Networking/NetworkMessage.h b/Source/Engine/Networking/NetworkMessage.h index e47e4c7d4..1c21af4a9 100644 --- a/Source/Engine/Networking/NetworkMessage.h +++ b/Source/Engine/Networking/NetworkMessage.h @@ -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" +/// +/// Message flags. +/// +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); + /// /// Network message structure. Provides raw data writing and reading to the message buffer. /// -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: /// /// The raw message buffer. /// API_FIELD() uint8* Buffer = nullptr; - /// - /// The unique, internal message identifier. - /// - API_FIELD() uint32 MessageId = 0; - /// /// The size in bytes of the buffer that this message has. /// API_FIELD() uint32 BufferSize = 0; - /// - /// The length in bytes of this message. - /// - API_FIELD() uint32 Length = 0; - /// /// The position in bytes in buffer where the next read/write will occur. /// API_FIELD() uint32 Position = 0; + /// + /// The unique, internal message identifier. + /// + API_FIELD() uint32 MessageId = 0; + + /// + /// Set of flags that describe the message state. + /// + API_FIELD() NetworkMessageFlags Flags = NetworkMessageFlags::None; + public: /// /// Initializes default values of the structure. @@ -50,12 +64,10 @@ public: /// /// Initializes values of the structure. /// - 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. /// /// The bytes that will be written. - /// The amount of bytes to write from the bytes pointer. - FORCE_INLINE void WriteBytes(const uint8* bytes, const int32 numBytes) + /// The amount of bytes to write from the pointer. + 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; } /// /// Reads raw bytes from the message into the given byte array. /// - /// - /// The buffer pointer that will be used to store the bytes. - /// Should be of the same length as length or longer. - /// - /// The minimal amount of bytes that the buffer contains. - FORCE_INLINE void ReadBytes(uint8* bytes, const int32 numBytes) + /// The buffer pointer that will be used to store the bytes. Should be of the same length as length or longer. + /// The minimal amount of bytes that the buffer contains. + 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; } /// /// Skips bytes from the message. /// - /// Amount of bytes to skip. + /// Amount of bytes to skip. /// Pointer to skipped data beginning. - 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 FORCE_INLINE void WriteStructure(const T& data) { - WriteBytes((const uint8*)&data, sizeof(data)); + WriteBytes(&data, sizeof(data)); } template 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(&value), sizeof(type)); } \ - FORCE_INLINE type Read##name() { type value = 0; ReadBytes(reinterpret_cast(&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: /// /// Writes data of type Vector2 into the message. /// - 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: /// /// Reads and returns data of type Vector2 from the message. /// - FORCE_INLINE Vector2 ReadVector2() + Vector2 ReadVector2() { return Vector2(ReadSingle(), ReadSingle()); } @@ -151,7 +171,7 @@ public: /// /// Writes data of type Vector3 into the message. /// - 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: /// /// Reads and returns data of type Vector3 from the message. /// - FORCE_INLINE Vector3 ReadVector3() + Vector3 ReadVector3() { return Vector3(ReadSingle(), ReadSingle(), ReadSingle()); } @@ -169,7 +189,7 @@ public: /// /// Writes data of type Vector4 into the message. /// - 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: /// /// Reads and returns data of type Vector4 from the message. /// - FORCE_INLINE Vector4 ReadVector4() + Vector4 ReadVector4() { return Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); } @@ -188,62 +208,73 @@ public: /// /// Writes data of type Quaternion into the message. /// - 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)); } /// /// Reads and returns data of type Quaternion from the message. /// - FORCE_INLINE Quaternion ReadQuaternion() + Quaternion ReadQuaternion() { - return Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); + Quaternion result = Quaternion::Identity; + ReadBytes(&result, sizeof(Quaternion)); + return result; } /// /// Writes data of type String into the message. UTF-16 encoded. /// - 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)); } /// /// Writes data of type String into the message. /// - 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()); } /// /// Reads and returns data of type String from the message. UTF-16 encoded. Data valid within message lifetime. /// - 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; } /// /// Reads and returns data of type String from the message. ANSI encoded. Data valid within message lifetime. /// - 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; } /// /// Writes data of type Guid into the message. /// - FORCE_INLINE void WriteGuid(const Guid& value) + void WriteGuid(const Guid& value) { WriteBytes((const uint8*)&value, sizeof(Guid)); } @@ -251,9 +282,9 @@ public: /// /// Reads and returns data of type Guid from the message. /// - FORCE_INLINE Guid ReadGuid() + Guid ReadGuid() { - Guid value; + Guid value = Guid::Empty; ReadBytes((uint8*)&value, sizeof(Guid)); return value; } diff --git a/Source/Engine/Networking/NetworkPeer.cpp b/Source/Engine/Networking/NetworkPeer.cpp index 7491691a4..558ea6aa7 100644 --- a/Source/Engine/Networking/NetworkPeer.cpp +++ b/Source/Engine/Networking/NetworkPeer.cpp @@ -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& targets) { - ASSERT(message.IsValid()); + CHECK_RETURN(message.IsValid(), true); + CHECK_RETURN(EnumHasNoneFlags(message.Flags, NetworkMessageFlags::HasError), true); NetworkDriver->SendMessage(channelType, message, targets); diff --git a/Source/Engine/Networking/NetworkPeer.h b/Source/Engine/Networking/NetworkPeer.h index 949e216d6..ad64fe163 100644 --- a/Source/Engine/Networking/NetworkPeer.h +++ b/Source/Engine/Networking/NetworkPeer.h @@ -98,10 +98,8 @@ public: /// The channel to send the message over. /// The message. /// Can be used only by the client! - /// - /// Do not recycle the message after calling this. - /// This function automatically recycles the message. - /// + /// Do not recycle the message after calling this. This function automatically recycles the message. + /// True if failed to send a message, otherwise false. API_FUNCTION() bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message); /// @@ -111,10 +109,8 @@ public: /// The message. /// The client connection to send the message to. /// Can be used only by the server! - /// - /// Do not recycle the message after calling this. - /// This function automatically recycles the message. - /// + /// Do not recycle the message after calling this. This function automatically recycles the message. + /// True if failed to send a message, otherwise false. API_FUNCTION() bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const NetworkConnection& target); /// @@ -124,10 +120,8 @@ public: /// The message. /// The connections list to send the message to. /// Can be used only by the server! - /// - /// Do not recycle the message after calling this. - /// This function automatically recycles the message. - /// + /// Do not recycle the message after calling this. This function automatically recycles the message. + /// True if failed to send a message, otherwise false. API_FUNCTION() bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const Array& targets); /// diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 2ee14508a..4952f57a9 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -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(argsStream->GetBuffer(), argsStream->GetPosition())); + if (argsStream) + rpc.ArgsData.Copy(Span(argsStream->GetBuffer(), argsStream->GetPosition())); rpc.Targets.Copy(targetIds); ObjectsLock.Unlock(); diff --git a/Source/Engine/Networking/NetworkStream.cpp b/Source/Engine/Networking/NetworkStream.cpp index 1542c98bd..4cd013fa5 100644 --- a/Source/Engine/Networking/NetworkStream.cpp +++ b/Source/Engine/Networking/NetworkStream.cpp @@ -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() diff --git a/Source/Engine/Networking/NetworkStream.cs b/Source/Engine/Networking/NetworkStream.cs index c946ad82f..0edc53b03 100644 --- a/Source/Engine/Networking/NetworkStream.cs +++ b/Source/Engine/Networking/NetworkStream.cs @@ -11,7 +11,7 @@ namespace FlaxEngine.Networking /// Writes raw bytes into the message. /// /// The bytes that will be written. - /// The amount of bytes to write from the bytes pointer. + /// The amount of bytes to write from the pointer. public void WriteBytes(byte* bytes, int length) { WriteData(new IntPtr(bytes), length); @@ -20,8 +20,7 @@ namespace FlaxEngine.Networking /// /// Reads raw bytes from the message into the given byte array. /// - /// The buffer pointer that will be used to store the bytes. Should be of the same length as length or longer. - /// + /// The buffer pointer that will be used to store the bytes. Should be of the same length as length or longer. /// The minimal amount of bytes that the buffer contains. public void ReadBytes(byte* buffer, int length) { @@ -285,8 +284,12 @@ namespace FlaxEngine.Networking /// 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 } /// @@ -302,9 +305,12 @@ namespace FlaxEngine.Networking /// 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 } /// @@ -320,10 +326,12 @@ namespace FlaxEngine.Networking /// 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 } /// @@ -339,8 +347,7 @@ namespace FlaxEngine.Networking /// public void WriteFloat2(Float2 value) { - WriteSingle(value.X); - WriteSingle(value.Y); + WriteBytes((byte*)&value, sizeof(Float2)); } /// @@ -356,9 +363,7 @@ namespace FlaxEngine.Networking /// public void WriteFloat3(Float3 value) { - WriteSingle(value.X); - WriteSingle(value.Y); - WriteSingle(value.Z); + WriteBytes((byte*)&value, sizeof(Float3)); } /// @@ -374,10 +379,7 @@ namespace FlaxEngine.Networking /// public void WriteFloat4(Float4 value) { - WriteSingle(value.X); - WriteSingle(value.Y); - WriteSingle(value.Z); - WriteSingle(value.W); + WriteBytes((byte*)&value, sizeof(Float4)); } /// @@ -393,10 +395,8 @@ namespace FlaxEngine.Networking /// 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)); } /// @@ -404,7 +404,10 @@ namespace FlaxEngine.Networking /// 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; } /// diff --git a/Source/Engine/Networking/NetworkStream.h b/Source/Engine/Networking/NetworkStream.h index 19b75b14b..bbe21f143 100644 --- a/Source/Engine/Networking/NetworkStream.h +++ b/Source/Engine/Networking/NetworkStream.h @@ -83,6 +83,7 @@ public: public: // [Stream] + bool HasError() const override; void Flush() override; void Close() override; uint32 GetLength() override;