From 3347887432b2e08ba753c39cede49aeb3cebadc3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 14 Sep 2022 14:54:32 +0200 Subject: [PATCH] Network objects replication impl progress --- .../Engine/Networking/INetworkSerializable.h | 28 ++ Source/Engine/Networking/NetworkClient.cs | 2 - Source/Engine/Networking/NetworkInternal.h | 12 + Source/Engine/Networking/NetworkManager.cpp | 32 +- Source/Engine/Networking/NetworkManager.h | 5 + .../Engine/Networking/NetworkReplicator.cpp | 169 ++++++++ Source/Engine/Networking/NetworkReplicator.h | 23 ++ Source/Engine/Networking/NetworkStream.cpp | 113 ++++++ Source/Engine/Networking/NetworkStream.cs | 372 ++++++++++++++++++ Source/Engine/Networking/NetworkStream.h | 62 +++ Source/Engine/Networking/Types.h | 2 + 11 files changed, 805 insertions(+), 15 deletions(-) create mode 100644 Source/Engine/Networking/INetworkSerializable.h create mode 100644 Source/Engine/Networking/NetworkInternal.h create mode 100644 Source/Engine/Networking/NetworkReplicator.cpp create mode 100644 Source/Engine/Networking/NetworkReplicator.h create mode 100644 Source/Engine/Networking/NetworkStream.cpp create mode 100644 Source/Engine/Networking/NetworkStream.cs create mode 100644 Source/Engine/Networking/NetworkStream.h diff --git a/Source/Engine/Networking/INetworkSerializable.h b/Source/Engine/Networking/INetworkSerializable.h new file mode 100644 index 000000000..b0cc649bb --- /dev/null +++ b/Source/Engine/Networking/INetworkSerializable.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Compiler.h" +#include "Engine/Core/Config.h" + +class NetworkStream; + +/// +/// Interface for values and objects that can be serialized/deserialized for network replication. +/// +API_INTERFACE(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API INetworkSerializable +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(INetworkSerializable); +public: + /// + /// Serializes object to the output stream. + /// + /// The output stream to write serialized data. + API_FUNCTION() virtual void Serialize(NetworkStream* stream) = 0; + + /// + /// Deserializes object from the input stream. + /// + /// The input stream to read serialized data. + API_FUNCTION() virtual void Deserialize(NetworkStream* stream) = 0; +}; diff --git a/Source/Engine/Networking/NetworkClient.cs b/Source/Engine/Networking/NetworkClient.cs index 1eb733cdd..dd995198a 100644 --- a/Source/Engine/Networking/NetworkClient.cs +++ b/Source/Engine/Networking/NetworkClient.cs @@ -1,7 +1,5 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. -using System; - namespace FlaxEngine.Networking { partial class NetworkClient diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h new file mode 100644 index 000000000..8bbd505bb --- /dev/null +++ b/Source/Engine/Networking/NetworkInternal.h @@ -0,0 +1,12 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Types.h" + +class NetworkInternal +{ +public: + static void NetworkReplicatorClear(); + static void NetworkReplicatorUpdate(); +}; diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index 60d993011..cf601f7d7 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -6,6 +6,7 @@ #include "NetworkEvent.h" #include "NetworkChannelType.h" #include "NetworkSettings.h" +#include "NetworkInternal.h" #include "FlaxEngine.Gen.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Array.h" @@ -15,6 +16,7 @@ #include "Engine/Scripting/Scripting.h" float NetworkManager::NetworkFPS = 60.0f; +NetworkPeer* NetworkManager::Peer = nullptr; NetworkManagerMode NetworkManager::Mode = NetworkManagerMode::Offline; NetworkConnectionState NetworkManager::State = NetworkConnectionState::Offline; NetworkClient* NetworkManager::LocalClient = nullptr; @@ -109,7 +111,6 @@ void OnNetworkMessageHandshakeReply(NetworkEvent& event, NetworkClient* client, namespace { - NetworkPeer* Peer = nullptr; uint32 GameProtocolVersion = 0; double LastUpdateTime = 0; @@ -144,7 +145,7 @@ NetworkManagerService NetworkManagerServiceInstance; bool StartPeer() { PROFILE_CPU(); - ASSERT_LOW_LAYER(!Peer); + ASSERT_LOW_LAYER(!NetworkManager::Peer); NetworkManager::State = NetworkConnectionState::Connecting; NetworkManager::StateChanged(); const auto& settings = *NetworkSettings::Get(); @@ -172,8 +173,8 @@ bool StartPeer() return true; } networkConfig.NetworkDriver = ScriptingObject::NewObject(networkDriverType); - Peer = NetworkPeer::CreatePeer(networkConfig); - if (!Peer) + NetworkManager::Peer = NetworkPeer::CreatePeer(networkConfig); + if (!NetworkManager::Peer) { LOG(Error, "Failed to create Network Peer at {0}:{1}", networkConfig.Address, networkConfig.Port); return true; @@ -184,13 +185,13 @@ bool StartPeer() void StopPeer() { - if (!Peer) + if (!NetworkManager::Peer) return; PROFILE_CPU(); if (NetworkManager::Mode == NetworkManagerMode::Client) - Peer->Disconnect(); - NetworkPeer::ShutdownPeer(Peer); - Peer = nullptr; + NetworkManager::Peer->Disconnect(); + NetworkPeer::ShutdownPeer(NetworkManager::Peer); + NetworkManager::Peer = nullptr; } void NetworkSettings::Apply() @@ -283,6 +284,7 @@ void NetworkManager::Stop() client->State = NetworkConnectionState::Disconnecting; StateChanged(); + NetworkInternal::NetworkReplicatorClear(); for (int32 i = Clients.Count() - 1; i >= 0; i--) { NetworkClient* client = Clients[i]; @@ -312,11 +314,12 @@ void NetworkManagerService::Update() return; PROFILE_CPU(); LastUpdateTime = currentTime; + auto peer = NetworkManager::Peer; // TODO: convert into TaskGraphSystems and use async jobs // Process network messages NetworkEvent event; - while (Peer->PopEvent(event)) + while (peer->PopEvent(event)) { switch (event.EventType) { @@ -347,10 +350,10 @@ void NetworkManagerService::Update() msgData.Platform = (byte)connectionData.Platform; msgData.Architecture = (byte)connectionData.Architecture; msgData.PayloadDataSize = (uint16)connectionData.PayloadData.Count(); - NetworkMessage msg = Peer->BeginSendMessage(); + NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); msg.WriteBytes(connectionData.PayloadData.Get(), connectionData.PayloadData.Count()); - Peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); + peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); } else { @@ -397,15 +400,18 @@ void NetworkManagerService::Update() uint8 id = *event.Message.Buffer; if (id < (uint8)NetworkMessageIDs::MAX) { - MessageHandlers[id](event, client, Peer); + MessageHandlers[id](event, client, peer); } else { LOG(Warning, "Unknown message id={0} from connection {1}", id, event.Sender.ConnectionId); } } - Peer->RecycleMessage(event.Message); + peer->RecycleMessage(event.Message); break; } } + + // Update replication + NetworkInternal::NetworkReplicatorUpdate(); } diff --git a/Source/Engine/Networking/NetworkManager.h b/Source/Engine/Networking/NetworkManager.h index 23dd4ead6..6d297ed4f 100644 --- a/Source/Engine/Networking/NetworkManager.h +++ b/Source/Engine/Networking/NetworkManager.h @@ -54,6 +54,11 @@ public: /// API_FIELD() static float NetworkFPS; + /// + /// Current network peer (low-level). + /// + API_FIELD(ReadOnly) static NetworkPeer* Peer; + /// /// Current manager mode. /// diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp new file mode 100644 index 000000000..09e463c56 --- /dev/null +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -0,0 +1,169 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "NetworkReplicator.h" +#include "NetworkClient.h" +#include "NetworkManager.h" +#include "NetworkInternal.h" +#include "NetworkStream.h" +#include "INetworkSerializable.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Collections/HashSet.h" +#include "Engine/Platform/CriticalSection.h" +#include "Engine/Engine/EngineService.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Threading/Threading.h" + +// Enables verbose logging for Network Replicator actions (dev-only) +#define NETWORK_REPLICATOR_DEBUG_LOG 1 + +struct NetworkReplicatedObject +{ + ScriptingObjectReference Object; + Guid OwnerId; +#if NETWORK_REPLICATOR_DEBUG_LOG + Guid ObjectId; + bool InvalidTypeWarn = false; +#endif + + bool operator==(const NetworkReplicatedObject& other) const + { + return Object == other.Object; + } + + bool operator==(const ScriptingObject* other) const + { + return Object == other; + } +}; + +#if NETWORK_REPLICATOR_DEBUG_LOG +#include "Engine/Core/Formatting.h" +DEFINE_DEFAULT_FORMATTING(NetworkReplicatedObject, "{}", v.ObjectId); +#endif + +inline uint32 GetHash(const NetworkReplicatedObject& key) +{ + return GetHash(key.Object.Get()); +} + +namespace +{ + CriticalSection ObjectsLock; + HashSet Objects; + NetworkStream* CachedStream = nullptr; +} + +class NetworkReplicationService : public EngineService +{ +public: + NetworkReplicationService() + : EngineService(TEXT("Network Replication"), 1100) + { + } + + void Dispose() override; +}; + +void NetworkReplicationService::Dispose() +{ + NetworkInternal::NetworkReplicatorClear(); +} + +NetworkReplicationService NetworkReplicationServiceInstance; + +void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* owner) +{ + if (!obj || NetworkManager::State == NetworkConnectionState::Offline) + return; + CHECK(owner && owner != obj); + ScopeLock lock(ObjectsLock); + if (Objects.Contains(obj)) + return; + + // Add object to the list + NetworkReplicatedObject item; + item.Object = obj; + item.OwnerId = owner->GetID(); +#if NETWORK_REPLICATOR_DEBUG_LOG + item.ObjectId = obj->GetID(); + LOG(Info, "[NetworkReplicator] Add new object {}:{}, owned by {}:{}", item, obj->GetType().ToString(), item.OwnerId, owner->GetType().ToString()); +#endif + Objects.Add(MoveTemp(item)); +} + +void NetworkInternal::NetworkReplicatorClear() +{ + ScopeLock lock(ObjectsLock); + + // Cleanup +#if NETWORK_REPLICATOR_DEBUG_LOG + LOG(Info, "[NetworkReplicator] Shutdown"); +#endif + Objects.Clear(); + Objects.SetCapacity(0); + SAFE_DELETE(CachedStream); +} + +void NetworkInternal::NetworkReplicatorUpdate() +{ + PROFILE_CPU(); + ScopeLock lock(ObjectsLock); + if (Objects.Count() == 0) + return; + if (CachedStream == nullptr) + CachedStream = New(); + // TODO: introduce NetworkReplicationHierarchy to optimize objects replication in large worlds (eg. batched culling networked scene objects that are too far from certain client to be relevant) + // TODO: per-object sync interval (in frames) - could be scaled by hierarchy (eg. game could slow down sync rate for objects far from player) + // TODO: network authority (eg. object owned by client) + + if (NetworkManager::IsClient()) + { + // TODO: client logic to apply replication changes + } + else + { + // Brute force synchronize all networked objects with clients + for (auto it = Objects.Begin(); it.IsNotEnd(); ++it) + { + auto& item = it->Item; + ScriptingObject* obj = item.Object.Get(); + if (!obj) + { + // Object got deleted +#if NETWORK_REPLICATOR_DEBUG_LOG + LOG(Info, "[NetworkReplicator] Remove object {}, owned by {}", item.Object, item.OwnerId); +#endif + Objects.Remove(it); + continue; + } + + // Serialize object + // TODO: cache per-type serialization thunk to boost CPU performance + CachedStream->Initialize(1024); + if (auto* serializable = ScriptingObject::ToInterface(obj)) + { + serializable->Serialize(CachedStream); + } + else + { +#if NETWORK_REPLICATOR_DEBUG_LOG + if (!item.InvalidTypeWarn) + { + item.InvalidTypeWarn = true; + LOG(Error, "[NetworkReplicator] Cannot serialize object {} (missing serialization logic)", item); + } +#endif + continue; + } + // TODO: how to serialize object? in memory to MemoryWriteStream? handle both C++ and C# without any memory alloc! + + // Brute force object to all clients + for (NetworkClient* client : NetworkManager::Clients) + { + // TODO: split object data (eg. more messages) if needed + // TODO: send message from Peer to client->Connection + } + } + } +} diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h new file mode 100644 index 000000000..f66815a5e --- /dev/null +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -0,0 +1,23 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Types.h" +#include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Scripting/ScriptingType.h" + +/// +/// High-level networking replication system for game objects. +/// +API_CLASS(static, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicator +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkReplicator); +public: + /// + /// Adds the object to the network replication system. + /// + /// Does nothing if network is offline. + /// The object to replicate. + /// The owner of the object (eg. player that spawned it). + API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* owner); +}; diff --git a/Source/Engine/Networking/NetworkStream.cpp b/Source/Engine/Networking/NetworkStream.cpp new file mode 100644 index 000000000..99e80cbc6 --- /dev/null +++ b/Source/Engine/Networking/NetworkStream.cpp @@ -0,0 +1,113 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "NetworkStream.h" + +NetworkStream::NetworkStream(const SpawnParams& params) + : ScriptingObject(params) + , ReadStream() + , WriteStream() +{ +} + +NetworkStream::~NetworkStream() +{ + if (_allocated) + Allocator::Free(_buffer); +} + +void NetworkStream::Initialize(uint32 minCapacity) +{ + // Unlink buffer if was reading from memory + if (!_allocated) + _buffer = nullptr; + + // Allocate if buffer is missing or too small + if (!_buffer || _length < minCapacity) + { + // Release previous + if (_buffer) + Allocator::Free(_buffer); + + // Allocate new one + _buffer = (byte*)Allocator::Allocate(minCapacity); + _length = minCapacity; + _allocated = true; + } + + // Reset pointer to the start + _position = _buffer; +} + +void NetworkStream::Flush() +{ + // Nothing to do +} + +void NetworkStream::Close() +{ + if (_allocated) + Allocator::Free(_buffer); + _position = _buffer = nullptr; + _length = 0; + _allocated = false; +} + +uint32 NetworkStream::GetLength() +{ + return _length; +} + +uint32 NetworkStream::GetPosition() +{ + return static_cast(_position - _buffer); +} + +void NetworkStream::SetPosition(uint32 seek) +{ + ASSERT(_length > 0); + _position = _buffer + seek; +} + +void NetworkStream::ReadBytes(void* data, uint32 bytes) +{ + if (bytes > 0) + { + ASSERT(data && GetLength() - GetPosition() >= bytes); + Platform::MemoryCopy(data, _position, bytes); + _position += bytes; + } +} + +void NetworkStream::WriteBytes(const void* data, uint32 bytes) +{ + // Calculate current position + const uint32 position = GetPosition(); + + // Check if there is need to update a buffer size + if (_length - position < bytes) + { + // Perform reallocation + uint32 newLength = _length != 0 ? _length * 2 : 256; + while (newLength < position + bytes) + newLength *= 2; + byte* newBuf = (byte*)Allocator::Allocate(newLength); + if (newBuf == nullptr) + { + OUT_OF_MEMORY; + } + if (_buffer && _length) + Platform::MemoryCopy(newBuf, _buffer, _length); + if (_allocated) + Allocator::Free(_buffer); + + // Update state + _buffer = newBuf; + _length = newLength; + _position = _buffer + position; + _allocated = true; + } + + // Copy data + Platform::MemoryCopy(_position, data, bytes); + _position += bytes; +} diff --git a/Source/Engine/Networking/NetworkStream.cs b/Source/Engine/Networking/NetworkStream.cs new file mode 100644 index 000000000..e0cf57108 --- /dev/null +++ b/Source/Engine/Networking/NetworkStream.cs @@ -0,0 +1,372 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +using System; +using System.Text; + +namespace FlaxEngine.Networking +{ + unsafe partial class NetworkStream + { + /// + /// Writes raw bytes into the message. + /// + /// The bytes that will be written. + /// The amount of bytes to write from the bytes pointer. + public void WriteBytes(byte* bytes, int length) + { + WriteData(new IntPtr(bytes), 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. + public void ReadBytes(byte* buffer, int length) + { + ReadData(new IntPtr(buffer), length); + } + + /// + /// Writes raw bytes into the message. + /// + /// The bytes that will be written. + /// The amount of bytes to write from the bytes array. + public void WriteBytes(byte[] bytes, int length) + { + fixed (byte* bytesPtr = bytes) + WriteData(new IntPtr(bytesPtr), length); + } + + /// + /// 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 minimal amount of bytes that the buffer contains. + public void ReadBytes(byte[] buffer, int length) + { + fixed (byte* bufferPtr = buffer) + ReadData(new IntPtr(bufferPtr), length); + } + + /// + /// Writes data of type into the message. + /// + public void WriteInt64(long value) + { + WriteBytes((byte*)&value, sizeof(long)); + } + + /// + /// Reads and returns data of type from the message. + /// + public long ReadInt64() + { + long value = 0; + ReadBytes((byte*)&value, sizeof(long)); + return value; + } + + /// + /// Writes data of type into the message. + /// + public void WriteInt32(int value) + { + WriteBytes((byte*)&value, sizeof(int)); + } + + /// + /// Reads and returns data of type from the message. + /// + public int ReadInt32() + { + int value = 0; + ReadBytes((byte*)&value, sizeof(int)); + return value; + } + + /// + /// Writes data of type into the message. + /// + public void WriteInt16(short value) + { + WriteBytes((byte*)&value, sizeof(short)); + } + + /// + /// Reads and returns data of type from the message. + /// + public short ReadInt16() + { + short value = 0; + ReadBytes((byte*)&value, sizeof(short)); + return value; + } + + /// + /// Writes data of type into the message. + /// + public void WriteSByte(sbyte value) + { + WriteBytes((byte*)&value, sizeof(sbyte)); + } + + /// + /// Reads and returns data of type from the message. + /// + public sbyte ReadSByte() + { + sbyte value = 0; + ReadBytes((byte*)&value, sizeof(sbyte)); + return value; + } + + /// + /// Writes data of type into the message. + /// + public void WriteUInt64(ulong value) + { + WriteBytes((byte*)&value, sizeof(ulong)); + } + + /// + /// Reads and returns data of type from the message. + /// + public ulong ReadUInt64() + { + ulong value = 0; + ReadBytes((byte*)&value, sizeof(ulong)); + return value; + } + + /// + /// Writes data of type into the message. + /// + public void WriteUInt32(uint value) + { + WriteBytes((byte*)&value, sizeof(uint)); + } + + /// + /// Reads and returns data of type from the message. + /// + public uint ReadUInt32() + { + uint value = 0; + ReadBytes((byte*)&value, sizeof(uint)); + return value; + } + + /// + /// Writes data of type into the message. + /// + public void WriteUInt16(ushort value) + { + WriteBytes((byte*)&value, sizeof(ushort)); + } + + /// + /// Reads and returns data of type from the message. + /// + public ushort ReadUInt16() + { + ushort value = 0; + ReadBytes((byte*)&value, sizeof(ushort)); + return value; + } + + /// + /// Writes data of type into the message. + /// + public void WriteByte(byte value) + { + WriteBytes(&value, sizeof(byte)); + } + + /// + /// Reads and returns data of type from the message. + /// + public byte ReadByte() + { + byte value = 0; + ReadBytes(&value, sizeof(byte)); + return value; + } + + /// + /// Writes data of type into the message. + /// + public void WriteSingle(float value) + { + WriteBytes((byte*)&value, sizeof(float)); + } + + /// + /// Reads and returns data of type from the message. + /// + public float ReadSingle() + { + float value = 0.0f; + ReadBytes((byte*)&value, sizeof(float)); + return value; + } + + /// + /// Writes data of type into the message. + /// + public void WriteDouble(double value) + { + WriteBytes((byte*)&value, sizeof(double)); + } + + /// + /// Reads and returns data of type from the message. + /// + public double ReadDouble() + { + double value = 0.0; + ReadBytes((byte*)&value, sizeof(double)); + return value; + } + + /// + /// Writes data of type into the message. UTF-16 encoded. + /// + 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); + } + + /// + /// Reads and returns data of type from the message. UTF-16 encoded. + /// + 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); + } + + /// + /// Writes data of type into the message. + /// + public void WriteGuid(Guid value) + { + WriteBytes((byte*)&value, sizeof(Guid)); + } + + /// + /// Reads and returns data of type from the message. + /// + public Guid ReadGuid() + { + var guidData = stackalloc Guid[1]; + ReadBytes((byte*)guidData, sizeof(Guid)); + return guidData[0]; + } + + /// + /// Writes data of type into the message. + /// + public void WriteVector2(Vector2 value) + { + WriteSingle((float)value.X); + WriteSingle((float)value.Y); + } + + /// + /// Reads and returns data of type from the message. + /// + public Vector2 ReadVector2() + { + return new Vector2(ReadSingle(), ReadSingle()); + } + + /// + /// Writes data of type into the message. + /// + public void WriteVector3(Vector3 value) + { + WriteSingle((float)value.X); + WriteSingle((float)value.Y); + WriteSingle((float)value.Z); + } + + /// + /// Reads and returns data of type from the message. + /// + public Vector3 ReadVector3() + { + return new Vector3(ReadSingle(), ReadSingle(), ReadSingle()); + } + + /// + /// Writes data of type into the message. + /// + public void WriteVector4(Vector4 value) + { + WriteSingle((float)value.X); + WriteSingle((float)value.Y); + WriteSingle((float)value.Z); + WriteSingle((float)value.W); + } + + /// + /// Reads and returns data of type from the message. + /// + public Vector4 ReadVector4() + { + return new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); + } + + /// + /// Writes data of type into the message. + /// + public void WriteQuaternion(Quaternion value) + { + WriteSingle(value.X); + WriteSingle(value.Y); + WriteSingle(value.Z); + WriteSingle(value.W); + } + + /// + /// Reads and returns data of type from the message. + /// + public Quaternion ReadQuaternion() + { + return new Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); + } + + /// + /// Writes data of type into the message. + /// + public void WriteBoolean(bool value) + { + WriteBytes((byte*)&value, sizeof(bool)); + } + + /// + /// Reads and returns data of type from the message. + /// + public bool ReadBoolean() + { + bool value = default; + ReadBytes((byte*)&value, sizeof(bool)); + return value; + } + } +} diff --git a/Source/Engine/Networking/NetworkStream.h b/Source/Engine/Networking/NetworkStream.h new file mode 100644 index 000000000..bbe35b207 --- /dev/null +++ b/Source/Engine/Networking/NetworkStream.h @@ -0,0 +1,62 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Serialization/ReadStream.h" +#include "Engine/Serialization/WriteStream.h" + +/// +/// Objects and values serialization stream for sending data over network. Uses memory buffer for both read and write operations. +/// +API_CLASS(sealed, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkStream final : public ScriptingObject, public ReadStream, public WriteStream +{ + DECLARE_SCRIPTING_TYPE(NetworkStream); +private: + byte* _buffer = nullptr; + byte* _position = nullptr; + uint32 _length = 0; + bool _allocated = false; + +public: + ~NetworkStream(); + + /// + /// Initializes the stream for writing. Allocates the memory or reuses already existing memory. Resets the current stream position to beginning. + /// + API_FUNCTION() void Initialize(uint32 minCapacity); + + /// + /// Writes bytes to the stream + /// + /// Data to write + /// Amount of bytes to write + API_FUNCTION() FORCE_INLINE void WriteData(const void* data, int32 bytes) + { + WriteBytes(data, bytes); + } + + /// + /// Reads bytes from the stream + /// + /// Data to write + /// Amount of bytes to write + API_FUNCTION() FORCE_INLINE void ReadData(void* data, int32 bytes) + { + ReadBytes(data, bytes); + } + +public: + // [Stream] + void Flush() override; + void Close() override; + uint32 GetLength() override; + uint32 GetPosition() override; + void SetPosition(uint32 seek) override; + + // [ReadStream] + void ReadBytes(void* data, uint32 bytes) override; + + // [WriteStream] + void WriteBytes(const void* data, uint32 bytes) override; +}; diff --git a/Source/Engine/Networking/Types.h b/Source/Engine/Networking/Types.h index fbf9f918f..23ee63d05 100644 --- a/Source/Engine/Networking/Types.h +++ b/Source/Engine/Networking/Types.h @@ -7,8 +7,10 @@ enum class NetworkEventType; enum class NetworkConnectionState; class INetworkDriver; +class INetworkSerializable; class NetworkPeer; class NetworkClient; +class NetworkStream; struct NetworkEvent; struct NetworkConnection;