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;