Network objects replication impl progress

This commit is contained in:
Wojciech Figat
2022-09-14 14:54:32 +02:00
parent 9a19717556
commit 3347887432
11 changed files with 805 additions and 15 deletions

View File

@@ -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;
/// <summary>
/// Interface for values and objects that can be serialized/deserialized for network replication.
/// </summary>
API_INTERFACE(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API INetworkSerializable
{
DECLARE_SCRIPTING_TYPE_MINIMAL(INetworkSerializable);
public:
/// <summary>
/// Serializes object to the output stream.
/// </summary>
/// <param name="stream">The output stream to write serialized data.</param>
API_FUNCTION() virtual void Serialize(NetworkStream* stream) = 0;
/// <summary>
/// Deserializes object from the input stream.
/// </summary>
/// <param name="stream">The input stream to read serialized data.</param>
API_FUNCTION() virtual void Deserialize(NetworkStream* stream) = 0;
};

View File

@@ -1,7 +1,5 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine.Networking
{
partial class NetworkClient

View File

@@ -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();
};

View File

@@ -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();
}

View File

@@ -54,6 +54,11 @@ public:
/// </summary>
API_FIELD() static float NetworkFPS;
/// <summary>
/// Current network peer (low-level).
/// </summary>
API_FIELD(ReadOnly) static NetworkPeer* Peer;
/// <summary>
/// Current manager mode.
/// </summary>

View File

@@ -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<ScriptingObject> 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<NetworkReplicatedObject> 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<NetworkStream>();
// 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<INetworkSerializable>(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
}
}
}
}

View File

@@ -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"
/// <summary>
/// High-level networking replication system for game objects.
/// </summary>
API_CLASS(static, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicator
{
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkReplicator);
public:
/// <summary>
/// Adds the object to the network replication system.
/// </summary>
/// <remarks>Does nothing if network is offline.</remarks>
/// <param name="obj">The object to replicate.</param>
/// <param name="owner">The owner of the object (eg. player that spawned it).</param>
API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* owner);
};

View File

@@ -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<uint32>(_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;
}

View File

@@ -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
{
/// <summary>
/// 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>
public void WriteBytes(byte* bytes, int length)
{
WriteData(new IntPtr(bytes), length);
}
/// <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="length">The minimal amount of bytes that the buffer contains.</param>
public void ReadBytes(byte* buffer, int length)
{
ReadData(new IntPtr(buffer), length);
}
/// <summary>
/// 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 array.</param>
public void WriteBytes(byte[] bytes, int length)
{
fixed (byte* bytesPtr = bytes)
WriteData(new IntPtr(bytesPtr), length);
}
/// <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="length">The minimal amount of bytes that the buffer contains.</param>
public void ReadBytes(byte[] buffer, int length)
{
fixed (byte* bufferPtr = buffer)
ReadData(new IntPtr(bufferPtr), length);
}
/// <summary>
/// Writes data of type <see cref="Int64"/> into the message.
/// </summary>
public void WriteInt64(long value)
{
WriteBytes((byte*)&value, sizeof(long));
}
/// <summary>
/// Reads and returns data of type <see cref="Int64"/> from the message.
/// </summary>
public long ReadInt64()
{
long value = 0;
ReadBytes((byte*)&value, sizeof(long));
return value;
}
/// <summary>
/// Writes data of type <see cref="Int32"/> into the message.
/// </summary>
public void WriteInt32(int value)
{
WriteBytes((byte*)&value, sizeof(int));
}
/// <summary>
/// Reads and returns data of type <see cref="Int32"/> from the message.
/// </summary>
public int ReadInt32()
{
int value = 0;
ReadBytes((byte*)&value, sizeof(int));
return value;
}
/// <summary>
/// Writes data of type <see cref="Int16"/> into the message.
/// </summary>
public void WriteInt16(short value)
{
WriteBytes((byte*)&value, sizeof(short));
}
/// <summary>
/// Reads and returns data of type <see cref="Int16"/> from the message.
/// </summary>
public short ReadInt16()
{
short value = 0;
ReadBytes((byte*)&value, sizeof(short));
return value;
}
/// <summary>
/// Writes data of type <see cref="SByte"/> into the message.
/// </summary>
public void WriteSByte(sbyte value)
{
WriteBytes((byte*)&value, sizeof(sbyte));
}
/// <summary>
/// Reads and returns data of type <see cref="SByte"/> from the message.
/// </summary>
public sbyte ReadSByte()
{
sbyte value = 0;
ReadBytes((byte*)&value, sizeof(sbyte));
return value;
}
/// <summary>
/// Writes data of type <see cref="UInt64"/> into the message.
/// </summary>
public void WriteUInt64(ulong value)
{
WriteBytes((byte*)&value, sizeof(ulong));
}
/// <summary>
/// Reads and returns data of type <see cref="UInt64"/> from the message.
/// </summary>
public ulong ReadUInt64()
{
ulong value = 0;
ReadBytes((byte*)&value, sizeof(ulong));
return value;
}
/// <summary>
/// Writes data of type <see cref="UInt32"/> into the message.
/// </summary>
public void WriteUInt32(uint value)
{
WriteBytes((byte*)&value, sizeof(uint));
}
/// <summary>
/// Reads and returns data of type <see cref="UInt32"/> from the message.
/// </summary>
public uint ReadUInt32()
{
uint value = 0;
ReadBytes((byte*)&value, sizeof(uint));
return value;
}
/// <summary>
/// Writes data of type <see cref="UInt16"/> into the message.
/// </summary>
public void WriteUInt16(ushort value)
{
WriteBytes((byte*)&value, sizeof(ushort));
}
/// <summary>
/// Reads and returns data of type <see cref="UInt16"/> from the message.
/// </summary>
public ushort ReadUInt16()
{
ushort value = 0;
ReadBytes((byte*)&value, sizeof(ushort));
return value;
}
/// <summary>
/// Writes data of type <see cref="Byte"/> into the message.
/// </summary>
public void WriteByte(byte value)
{
WriteBytes(&value, sizeof(byte));
}
/// <summary>
/// Reads and returns data of type <see cref="Byte"/> from the message.
/// </summary>
public byte ReadByte()
{
byte value = 0;
ReadBytes(&value, sizeof(byte));
return value;
}
/// <summary>
/// Writes data of type <see cref="Single"/> into the message.
/// </summary>
public void WriteSingle(float value)
{
WriteBytes((byte*)&value, sizeof(float));
}
/// <summary>
/// Reads and returns data of type <see cref="Single"/> from the message.
/// </summary>
public float ReadSingle()
{
float value = 0.0f;
ReadBytes((byte*)&value, sizeof(float));
return value;
}
/// <summary>
/// Writes data of type <see cref="Double"/> into the message.
/// </summary>
public void WriteDouble(double value)
{
WriteBytes((byte*)&value, sizeof(double));
}
/// <summary>
/// Reads and returns data of type <see cref="Double"/> from the message.
/// </summary>
public double ReadDouble()
{
double value = 0.0;
ReadBytes((byte*)&value, sizeof(double));
return value;
}
/// <summary>
/// Writes data of type <see cref="string"/> into the message. UTF-16 encoded.
/// </summary>
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);
}
/// <summary>
/// Reads and returns data of type <see cref="string"/> from the message. UTF-16 encoded.
/// </summary>
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);
}
/// <summary>
/// Writes data of type <see cref="Guid"/> into the message.
/// </summary>
public void WriteGuid(Guid value)
{
WriteBytes((byte*)&value, sizeof(Guid));
}
/// <summary>
/// Reads and returns data of type <see cref="Guid"/> from the message.
/// </summary>
public Guid ReadGuid()
{
var guidData = stackalloc Guid[1];
ReadBytes((byte*)guidData, sizeof(Guid));
return guidData[0];
}
/// <summary>
/// Writes data of type <see cref="Vector2"/> into the message.
/// </summary>
public void WriteVector2(Vector2 value)
{
WriteSingle((float)value.X);
WriteSingle((float)value.Y);
}
/// <summary>
/// Reads and returns data of type <see cref="Vector2"/> from the message.
/// </summary>
public Vector2 ReadVector2()
{
return new Vector2(ReadSingle(), ReadSingle());
}
/// <summary>
/// Writes data of type <see cref="Vector3"/> into the message.
/// </summary>
public void WriteVector3(Vector3 value)
{
WriteSingle((float)value.X);
WriteSingle((float)value.Y);
WriteSingle((float)value.Z);
}
/// <summary>
/// Reads and returns data of type <see cref="Vector3"/> from the message.
/// </summary>
public Vector3 ReadVector3()
{
return new Vector3(ReadSingle(), ReadSingle(), ReadSingle());
}
/// <summary>
/// Writes data of type <see cref="Vector4"/> into the message.
/// </summary>
public void WriteVector4(Vector4 value)
{
WriteSingle((float)value.X);
WriteSingle((float)value.Y);
WriteSingle((float)value.Z);
WriteSingle((float)value.W);
}
/// <summary>
/// Reads and returns data of type <see cref="Vector4"/> from the message.
/// </summary>
public Vector4 ReadVector4()
{
return new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
}
/// <summary>
/// Writes data of type <see cref="Quaternion"/> into the message.
/// </summary>
public void WriteQuaternion(Quaternion value)
{
WriteSingle(value.X);
WriteSingle(value.Y);
WriteSingle(value.Z);
WriteSingle(value.W);
}
/// <summary>
/// Reads and returns data of type <see cref="Quaternion"/> from the message.
/// </summary>
public Quaternion ReadQuaternion()
{
return new Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
}
/// <summary>
/// Writes data of type <see cref="Boolean"/> into the message.
/// </summary>
public void WriteBoolean(bool value)
{
WriteBytes((byte*)&value, sizeof(bool));
}
/// <summary>
/// Reads and returns data of type <see cref="Boolean"/> from the message.
/// </summary>
public bool ReadBoolean()
{
bool value = default;
ReadBytes((byte*)&value, sizeof(bool));
return value;
}
}
}

View File

@@ -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"
/// <summary>
/// Objects and values serialization stream for sending data over network. Uses memory buffer for both read and write operations.
/// </summary>
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();
/// <summary>
/// Initializes the stream for writing. Allocates the memory or reuses already existing memory. Resets the current stream position to beginning.
/// </summary>
API_FUNCTION() void Initialize(uint32 minCapacity);
/// <summary>
/// Writes bytes to the stream
/// </summary>
/// <param name="data">Data to write</param>
/// <param name="bytes">Amount of bytes to write</param>
API_FUNCTION() FORCE_INLINE void WriteData(const void* data, int32 bytes)
{
WriteBytes(data, bytes);
}
/// <summary>
/// Reads bytes from the stream
/// </summary>
/// <param name="data">Data to write</param>
/// <param name="bytes">Amount of bytes to write</param>
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;
};

View File

@@ -7,8 +7,10 @@ enum class NetworkEventType;
enum class NetworkConnectionState;
class INetworkDriver;
class INetworkSerializable;
class NetworkPeer;
class NetworkClient;
class NetworkStream;
struct NetworkEvent;
struct NetworkConnection;