optimizing network stuff

This commit is contained in:
2025-07-30 05:51:39 +02:00
parent ea99249fe2
commit 159e9adcd7
56 changed files with 2272 additions and 1781 deletions

View File

@@ -1,7 +1,4 @@
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Netcode;
using Unity.Collections;
using UnityEngine.Assertions;
namespace RebootKit.Engine.Network {
@@ -29,7 +26,7 @@ namespace RebootKit.Engine.Network {
return default;
}
public static void Deserialize<TEntity>(NativeArray<byte> data, ref TEntity entity)
public static void Deserialize<TEntity>(NativeSlice<byte> data, ref TEntity entity)
where TEntity : ISerializableEntity {
using NetworkBufferReader reader = new NetworkBufferReader(data);
if (reader.HasNext(data.Length)) {

View File

@@ -0,0 +1,62 @@
using System;
using Unity.Collections;
namespace RebootKit.Engine.Network {
public enum SendMode {
Reliable,
Unreliable
}
public interface INetworkManagerDelegate {
void OnServerStarted();
void OnServerStopped();
void OnClientStarted();
void OnClientStopped();
void OnClientConnected(ulong clientID);
void OnClientDisconnected(ulong clientID);
void OnMessageReceived(ulong senderID, NativeArray<byte> data);
}
public class NetworkManagerStats {
public ulong ReliableBytesSent { get; set; }
public ulong UnreliableBytesSent { get; set; }
public ulong BytesReceived { get; set; }
public double ReliableBytesSentPerSecond { get; set; }
public double UnreliableBytesSentPerSecond { get; set; }
public double BytesReceivedPerSecond { get; set; }
public int Ping { get; set; }
public void Reset() {
ReliableBytesSent = 0;
UnreliableBytesSent = 0;
BytesReceived = 0;
ReliableBytesSentPerSecond = 0.0;
UnreliableBytesSentPerSecond = 0.0;
BytesReceivedPerSecond = 0.0;
Ping = 0;
}
}
public interface INetworkManager : IDisposable {
INetworkManagerDelegate Delegate { get; set; }
ulong LocalClientID { get; }
NetworkManagerStats Stats { get; }
bool IsServer();
bool StartHost();
void StopHost();
bool IsClient();
bool StartClient();
void Disconnect();
void Send(ulong clientID, NativeSlice<byte> data, SendMode mode);
unsafe void Send(ulong clientID, byte* data, int length, SendMode mode);
void Tick();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 26612cc58cd249cf8d567501e253eab1
timeCreated: 1753138601

View File

@@ -7,7 +7,7 @@ using UnityEngine.Pool;
namespace RebootKit.Engine.Network {
public struct NetworkBufferReader : IDisposable {
class ReaderHandle {
public NativeArray<byte> Data;
public NativeSlice<byte> Data;
public int Position;
public bool IsBigEndian;
}
@@ -27,8 +27,7 @@ namespace RebootKit.Engine.Network {
ReaderHandle m_Handle;
public NetworkBufferReader(NativeArray<byte> data, int position = 0) {
Assert.IsTrue(data.IsCreated, "Trying to create a NetworkBufferReader with uncreated data.");
public NetworkBufferReader(NativeSlice<byte> data, int position = 0) {
Assert.IsTrue(position >= 0 && position <= data.Length,
"Position must be within the bounds of the data array.");
@@ -53,6 +52,11 @@ namespace RebootKit.Engine.Network {
Assert.IsTrue(HasNext(size),
$"Not enough data to read the requested size. Requested: {size}, Available: {m_Handle.Data.Length - m_Handle.Position}");
if (size <= 0) {
value = default;
return false;
}
value = new NativeArray<byte>(size, allocator);
for (int i = 0; i < size; i++) {
value[i] = m_Handle.Data[m_Handle.Position++];
@@ -61,6 +65,17 @@ namespace RebootKit.Engine.Network {
return true;
}
public bool Read(out NativeSlice<byte> value, int size) {
if (!HasNext(size)) {
value = default;
return false;
}
value = m_Handle.Data.Slice(m_Handle.Position, size);
m_Handle.Position += size;
return true;
}
public bool Read(out byte value) {
if (!HasNext(1)) {
value = 0;

View File

@@ -1,4 +1,5 @@
using System;
using System.Text;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Assertions;
@@ -179,7 +180,7 @@ namespace RebootKit.Engine.Network {
public void Write(ulong value) {
Assert.IsTrue(sizeof(ulong) == 8, "Size of ulong must be 8 bytes.");
Assert.IsTrue(WillFit(sizeof(ulong)), "Buffer overflow: Cannot write beyond capacity.");
Assert.IsTrue(WillFit(sizeof(ulong)), $"Buffer overflow: Cannot write beyond capacity. Current position: {m_Handle.Position}, Capacity: {m_Handle.Capacity}");
if (BitConverter.IsLittleEndian) {
Write((byte) (value & 0xFF));
@@ -312,5 +313,43 @@ namespace RebootKit.Engine.Network {
}
}
}
// @NOTE: Writes ascii characters as bytes, and prepends the length as a ushort.
public static int GetStringWriteLength(string value) {
if (value.Length > ushort.MaxValue) {
return sizeof(ushort);
}
if (string.IsNullOrEmpty(value)) {
return sizeof(ushort);
}
// Length prefix
int length = sizeof(ushort);
for (int i = 0; i < value.Length; i++) {
length += sizeof(char);
}
return length;
}
public void Write(string value) {
if (value.Length > ushort.MaxValue) {
Write((ushort) 0);
return;
}
if (string.IsNullOrEmpty(value)) {
Write((ushort) 0);
return;
}
Write((ushort)value.Length);
for (int i = 0; i < value.Length; i++) {
Write((byte)value[i]);
}
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using RebootKit.Engine;
using RebootKit.Engine.Network;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Assertions;
namespace RebootKit.Engine.Network {
enum NetworkMessageType : byte {
None = 0x00,
// @MARK: Server to client messages
SpawnActor = 0x01,
PossessActor = 0x02,
ActorsEventsList = 0x03,
ActorsCoreStatesUpdateList = 0x04,
ActorsStatesUpdateList = 0x05,
ActorsTransformUpdateList = 0x06,
SynchronizeGameState = 0x07,
// @MARK: Client to server messages
ClientSynchronizedGameState = 0x80,
ActorsCommandsList = 0x81,
}
struct NetworkMessageHeader : ISerializableEntity {
public ushort MagicNumber;
public byte Version;
public NetworkMessageType MessageType;
public const int k_HeaderSize = sizeof(ushort) + sizeof(byte) + sizeof(byte);
public int GetMaxBytes() {
return k_HeaderSize;
}
public void Serialize(NetworkBufferWriter writer) {
writer.Write(MagicNumber);
writer.Write(Version);
writer.Write((byte) MessageType);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out MagicNumber);
reader.Read(out Version);
reader.Read(out byte type);
MessageType = (NetworkMessageType) type;
}
}
class NetworkMessageBuffer : IDisposable {
NativeArray<byte> m_Data;
public NetworkBufferWriter Writer;
public NetworkMessageBuffer(Allocator allocator) {
m_Data = new NativeArray<byte>(RConsts.k_NetworkMessageMaxSize, allocator);
Writer = new NetworkBufferWriter(m_Data, 0);
}
public void Dispose() {
Writer.Dispose();
m_Data.Dispose();
}
public void Reset() {
Writer.Position = 0;
if (m_Data.IsCreated) {
unsafe {
UnsafeUtility.MemClear(m_Data.GetUnsafePtr(), m_Data.Length);
}
}
}
public NativeSlice<byte> GetDataSlice() {
return m_Data.Slice(0, Writer.Position);
}
public void WriteHeader(NetworkMessageType type, byte version) {
NetworkMessageBufferUtility.WriteHeader(Writer, type, version);
}
public NativeArray<byte> GetDataCopy(Allocator allocator) {
NativeSlice<byte> dataSlice = GetDataSlice();
NativeArray<byte> dataCopy = new NativeArray<byte>(dataSlice.Length, allocator);
dataSlice.CopyTo(dataCopy);
return dataCopy;
}
public int GetMessageContentSize() {
Assert.IsTrue(Writer.Position >= NetworkMessageHeader.k_HeaderSize,
"Writer position must be greater than or equal to header size.");
return Writer.Position - NetworkMessageHeader.k_HeaderSize;
}
}
}
static class NetworkMessageBufferUtility {
public static void WriteHeader(NetworkBufferWriter writer,
NetworkMessageType type,
byte version) {
NetworkMessageHeader header = new NetworkMessageHeader {
MagicNumber = RConsts.k_NetworkMessageMagic,
Version = version,
MessageType = type,
};
header.Serialize(writer);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e9d2fa991d31446db7fe1a4fef441a1d
timeCreated: 1753152667

View File

@@ -1,303 +0,0 @@
using System;
using System.Collections.Generic;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Simulation;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Assertions;
using UnityEngine.Pool;
namespace RebootKit.Engine.Network {
struct NetworkPacketHeader : ISerializableEntity {
public int MagicNumber;
public ushort Version;
public ushort EntityCount;
public static int GetEntityCountOffset() {
return sizeof(int) + sizeof(ushort);
}
public void Serialize(NetworkBufferWriter writer) {
writer.Write(MagicNumber);
writer.Write(Version);
writer.Write(EntityCount);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out MagicNumber);
reader.Read(out Version);
reader.Read(out EntityCount);
}
public int GetMaxBytes() {
return sizeof(int) + sizeof(ushort) * 2; // MagicNumber, Version, EntityCount
}
}
class NetworkPacket : IDisposable {
public static readonly IObjectPool<NetworkPacket> Pool = new ObjectPool<NetworkPacket>(
() => {
NetworkPacket packet = new NetworkPacket();
packet.Data = default;
packet.Writer = default;
return packet;
},
packet => {
// Packet is initialized after being retrieved from the pool
},
packet => {
packet.Dispose();
},
packet => {
packet.Dispose();
},
true,
16
);
public NativeArray<byte> Data;
public NetworkBufferWriter Writer;
public ushort EntityCount { get; private set; }
public void IncrementEntityCount() {
int originalPosition = Writer.Position;
EntityCount += 1;
Writer.Position = NetworkPacketHeader.GetEntityCountOffset(); // Reset position to write the entity count
Writer.Write(EntityCount);
Writer.Position = originalPosition;
}
public void Dispose() {
Data.Dispose();
Writer.Dispose();
EntityCount = 0;
}
}
enum NetworkDataType : byte {
None = 0x00,
ActorCoreState = 0x01,
ActorTransformSync = 0x02,
ActorState = 0x03,
ActorEvent = 0x04,
ActorCommand = 0x05,
SynchronizeActor = 0x07,
SpawnActor = 0x08,
}
struct NetworkDataHeader : ISerializableEntity {
public NetworkDataType Type;
public ulong ActorID;
public int DataSize;
public int GetMaxBytes() {
return sizeof(ulong) + sizeof(byte) + sizeof(int);
}
public void Serialize(NetworkBufferWriter writer) {
writer.Write((byte) Type);
writer.Write(ActorID);
writer.Write(DataSize);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out byte typeByte);
Type = (NetworkDataType) typeByte;
reader.Read(out ActorID);
reader.Read(out DataSize);
}
}
class NetworkPacketQueue : IDisposable {
static readonly Logger s_Logger = new Logger(nameof(NetworkPacketQueue));
readonly int m_PacketMaxSize;
readonly ushort m_Version;
internal readonly List<NetworkPacket> NetworkPackets = new List<NetworkPacket>();
public NetworkPacketQueue(int packetMaxSize, ushort version = 1) {
m_PacketMaxSize = packetMaxSize;
m_Version = version;
Assert.IsTrue(m_PacketMaxSize > 0, "Packet maximum size must be greater than zero.");
}
public void Dispose() {
foreach (NetworkPacket packet in NetworkPackets) {
packet.Data.Dispose();
}
NetworkPackets.Clear();
}
public void Clear() {
foreach (NetworkPacket packet in NetworkPackets) {
packet.Dispose();
}
NetworkPackets.Clear();
}
public void WriteActorState(ulong actorID, IActorData entity) {
Assert.IsTrue(entity.GetMaxBytes() <= m_PacketMaxSize,
$"Entity size {entity.GetMaxBytes()} exceeds packet max size {m_PacketMaxSize}.");
NetworkDataHeader header = new NetworkDataHeader {
Type = NetworkDataType.ActorState,
ActorID = actorID,
DataSize = entity.GetMaxBytes()
};
int bytesToWrite = header.GetMaxBytes() + entity.GetMaxBytes();
NetworkPacket packet = GetPacketToWriteTo(bytesToWrite);
header.Serialize(packet.Writer);
entity.Serialize(packet.Writer);
packet.IncrementEntityCount();
}
public void WriteActorTransformState(ulong actorID, ActorTransformSyncData transformData) {
NetworkDataHeader header = new NetworkDataHeader {
Type = NetworkDataType.ActorTransformSync,
ActorID = actorID,
DataSize = transformData.GetMaxBytes()
};
int bytesToWrite = header.GetMaxBytes() + transformData.GetMaxBytes();
NetworkPacket packet = GetPacketToWriteTo(bytesToWrite);
header.Serialize(packet.Writer);
transformData.Serialize(packet.Writer);
packet.IncrementEntityCount();
}
public void WriteActorCoreState(ulong actorID, ActorCoreStateSnapshot coreState) {
NetworkDataHeader header = new NetworkDataHeader {
Type = NetworkDataType.ActorCoreState,
ActorID = actorID,
DataSize = coreState.GetMaxBytes()
};
int bytesToWrite = header.GetMaxBytes() + coreState.GetMaxBytes();
NetworkPacket packet = GetPacketToWriteTo(bytesToWrite);
header.Serialize(packet.Writer);
coreState.Serialize(packet.Writer);
packet.IncrementEntityCount();
}
public void WriteSpawnActor(FixedString64Bytes assetGUID,
ulong actorID,
ActorCoreStateSnapshot coreState,
IActorData actorData) {
NetworkDataHeader header = new NetworkDataHeader {
Type = NetworkDataType.SpawnActor,
ActorID = actorID,
DataSize = 0
};
header.DataSize += sizeof(byte) * 64; // assetGUID
header.DataSize += coreState.GetMaxBytes();
header.DataSize += sizeof(ushort);
header.DataSize += actorData.GetMaxBytes();
NetworkPacket packet = GetPacketToWriteTo(header.GetMaxBytes() + header.DataSize);
header.Serialize(packet.Writer);
packet.Writer.Write(assetGUID);
coreState.Serialize(packet.Writer);
packet.Writer.Write((ushort) actorData.GetMaxBytes());
actorData.Serialize(packet.Writer);
packet.IncrementEntityCount();
}
public void WriteActorSynchronize(ulong actorID,
ActorCoreStateSnapshot coreState,
IActorData actorData) {
NetworkDataHeader header = new NetworkDataHeader {
Type = NetworkDataType.SynchronizeActor,
ActorID = actorID,
DataSize = 0
};
header.DataSize += coreState.GetMaxBytes();
header.DataSize += sizeof(ushort);
header.DataSize += actorData.GetMaxBytes();
NetworkPacket packet = GetPacketToWriteTo(header.GetMaxBytes() + header.DataSize);
header.Serialize(packet.Writer);
coreState.Serialize(packet.Writer);
packet.Writer.Write((ushort) actorData.GetMaxBytes());
actorData.Serialize(packet.Writer);
packet.IncrementEntityCount();
}
public void WriteActorEvent(ActorEvent actorEvent) {
NetworkDataHeader header = new NetworkDataHeader {
Type = NetworkDataType.ActorEvent,
ActorID = actorEvent.ActorID,
DataSize = actorEvent.GetMaxBytes()
};
NetworkPacket packet = GetPacketToWriteTo(header.GetMaxBytes() + header.DataSize);
header.Serialize(packet.Writer);
actorEvent.Serialize(packet.Writer);
packet.IncrementEntityCount();
}
public void WriteActorCommand(ActorCommand actorCommand) {
NetworkDataHeader header = new NetworkDataHeader {
Type = NetworkDataType.ActorCommand,
ActorID = actorCommand.ActorID,
DataSize = actorCommand.GetMaxBytes()
};
NetworkPacket packet = GetPacketToWriteTo(header.GetMaxBytes() + header.DataSize);
header.Serialize(packet.Writer);
actorCommand.Serialize(packet.Writer);
packet.IncrementEntityCount();
}
NetworkPacket GetPacketToWriteTo(int bytesToWrite) {
foreach (NetworkPacket networkPacket in NetworkPackets) {
if (networkPacket.Writer.WillFit(bytesToWrite)) {
return networkPacket;
}
}
Assert.IsTrue(bytesToWrite < m_PacketMaxSize,
$"Packet size {bytesToWrite} exceeds maximum allowed size {m_PacketMaxSize}.");
NetworkPacket packet = NetworkPacket.Pool.Get();
packet.Data = new NativeArray<byte>(m_PacketMaxSize, Allocator.Persistent);
unsafe {
void* ptr = packet.Data.GetUnsafePtr();
UnsafeUtility.MemClear(ptr, sizeof(byte) * packet.Data.Length);
}
packet.Writer = new NetworkBufferWriter(packet.Data, 0);
NetworkPacketHeader header = new NetworkPacketHeader {
MagicNumber = RConsts.k_NetworkPacketMagicNumber,
Version = m_Version,
EntityCount = 0 // Will be updated later
};
header.Serialize(packet.Writer);
NetworkPackets.Add(packet);
return packet;
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8a122573c79c4b3e9ef3bc2da3b09faa
timeCreated: 1752855419

View File

@@ -1,42 +1,39 @@
using System;
using System.Threading;
using System.Threading;
using Cysharp.Threading.Tasks;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Simulation;
using Unity.Netcode;
using UnityEngine;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootKit.Engine.Network {
public abstract class NetworkPlayerController : NetworkBehaviour {
public abstract class NetworkPlayerController : MonoBehaviour {
static readonly Logger s_Logger = new Logger(nameof(NetworkPlayerController));
ulong m_ActorIDToPossess;
ushort m_ActorIDToPossess;
CancellationTokenSource m_PossessionCancellationTokenSource = new CancellationTokenSource();
public Actor PossessedActor { get; private set; }
public void PossessActor(Actor actor) {
if (!IsServer) {
s_Logger.Error("PossessActor can only be called on the server.");
return;
}
internal void SetPossessedActor(ushort actorID) {
s_Logger.Info("Setting possessed actor to " + actorID);
if (actorID == 0) {
m_ActorIDToPossess = 0;
if (actor == null) {
s_Logger.Error("Cannot possess a null actor.");
return;
}
if (PossessedActor != null) {
PossessedActor = null;
OnUnpossessActor(PossessedActor);
}
} else {
m_PossessionCancellationTokenSource.Cancel();
m_PossessionCancellationTokenSource.Dispose();
m_PossessionCancellationTokenSource = new CancellationTokenSource();
PossessActorRpc(actor.ActorID, RpcTarget.Everyone);
WaitForActorToSpawnThenPossessAsync(actorID, m_PossessionCancellationTokenSource.Token).Forget();
}
}
[Rpc(SendTo.SpecifiedInParams)]
void PossessActorRpc(ulong actorID, RpcParams rpcParams) {
if (PossessedActor is not null) {
OnUnpossessActor(PossessedActor);
}
WaitForActorToSpawnThenPossessAsync(actorID, destroyCancellationToken).Forget();
}
async UniTask WaitForActorToSpawnThenPossessAsync(ulong actorID, CancellationToken cancellationToken) {
async UniTask WaitForActorToSpawnThenPossessAsync(ushort actorID, CancellationToken cancellationToken) {
Actor actor = null;
while (actor == null) {
actor = RR.FindSpawnedActor(actorID);
@@ -51,27 +48,6 @@ namespace RebootKit.Engine.Network {
OnPossessActor(actor);
}
public void UnPossessActor() {
if (!IsServer) {
s_Logger.Error("UnPossessActor can only be called on the server.");
return;
}
if (PossessedActor == null) {
return;
}
UnPossessActorRpc(RpcTarget.Everyone);
}
[Rpc(SendTo.SpecifiedInParams)]
void UnPossessActorRpc(RpcParams rpcParams) {
if (PossessedActor is not null) {
OnUnpossessActor(PossessedActor);
PossessedActor = null;
}
}
protected virtual void OnPossessActor(Actor actor) {
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
using Unity.Netcode;
namespace RebootKit.Engine.Network {
public abstract class NetworkWorldController : NetworkBehaviour {
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: edd521a69a6f4e43b97ec258adf499a6
timeCreated: 1751377120

View File

@@ -0,0 +1,291 @@
using System;
using System.Collections.Generic;
using R3;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Networking.Transport;
using UnityEngine;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootKit.Engine.Network {
// @NOTE: This won't probably be suited for production use.
public class UnityNetworkManager : INetworkManager {
static readonly Logger s_Logger = new Logger(nameof(UnityNetworkManager));
const ulong k_ServerClientID = 0;
NetworkDriver m_Driver;
// @MARK: Server specific stuff
bool m_IsServer;
readonly Dictionary<ulong, NetworkConnection> m_ServerConnections = new Dictionary<ulong, NetworkConnection>();
ulong m_ClientIDCounter = 1;
bool m_IsClient;
NetworkConnection m_ClientConnection;
public INetworkManagerDelegate Delegate { get; set; }
public ulong LocalClientID { get; private set; }
public NetworkManagerStats Stats { get; } = new NetworkManagerStats();
public ushort Port = 7777;
public UnityNetworkManager() {
m_Driver = NetworkDriver.Create();
if (!m_Driver.IsCreated) {
s_Logger.Error("Failed to create network driver");
return;
}
m_IsServer = false;
m_IsClient = false;
LocalClientID = 0; // This should be set to a unique ID for the local client
}
public void Dispose() {
if (m_Driver.IsCreated) {
m_Driver.Dispose();
}
m_ServerConnections.Clear();
m_ClientConnection = default;
}
float m_StatsTimer = 0.0f;
public void Tick() {
m_Driver.ScheduleUpdate().Complete();
if (IsServer()) {
ServerTick();
}
if (IsClient()) {
ClientTick();
}
if (IsServer() || IsClient()) {
m_StatsTimer -= Time.deltaTime;
if (m_StatsTimer <= 0.0f) {
m_StatsTimer = 1.0f;
Stats.ReliableBytesSentPerSecond = Stats.ReliableBytesSent;
Stats.UnreliableBytesSentPerSecond = Stats.UnreliableBytesSent;
Stats.BytesReceivedPerSecond = Stats.BytesReceived;
Stats.ReliableBytesSent = 0;
Stats.UnreliableBytesSent = 0;
Stats.BytesReceived = 0;
}
}
}
void ServerTick() {
using NativeList<ulong> clientIDsToRemove = new NativeList<ulong>(Allocator.Temp);
foreach ((ulong clientID, NetworkConnection connection) in m_ServerConnections) {
if (!connection.IsCreated) {
clientIDsToRemove.Add(clientID);
}
}
foreach (ulong clientID in clientIDsToRemove) {
m_ServerConnections.Remove(clientID);
Delegate?.OnClientDisconnected(clientID);
s_Logger.Info($"Client {clientID} disconnected");
}
NetworkConnection incomingConnection;
while ((incomingConnection = m_Driver.Accept()) != default) {
ulong newClientID = m_ClientIDCounter++;
m_ServerConnections.Add(newClientID, incomingConnection);
s_Logger.Info("Connection accepted: " + newClientID);
Delegate?.OnClientConnected(newClientID);
}
// @NOTE: Handle incoming messages from clients
clientIDsToRemove.Clear();
foreach ((ulong clientID, NetworkConnection connection) in m_ServerConnections) {
DataStreamReader stream;
NetworkEvent.Type cmd;
while ((cmd = m_Driver.PopEventForConnection(connection, out stream)) !=
NetworkEvent.Type.Empty) {
if (cmd == NetworkEvent.Type.Data) {
NativeArray<byte> data = new NativeArray<byte>(stream.Length, Allocator.Temp);
stream.ReadBytes(data);
Stats.BytesReceived += (ulong) stream.Length;
Delegate?.OnMessageReceived(clientID, data);
} else if (cmd == NetworkEvent.Type.Disconnect) {
clientIDsToRemove.Add(clientID);
}
}
}
// @TODO: Code duplication
foreach (ulong clientID in clientIDsToRemove) {
m_ServerConnections.Remove(clientID);
Delegate?.OnClientDisconnected(clientID);
s_Logger.Info($"Client {clientID} disconnected");
}
}
void ClientTick() {
DataStreamReader stream;
NetworkEvent.Type cmd;
while ((cmd = m_ClientConnection.PopEvent(m_Driver, out stream)) != NetworkEvent.Type.Empty) {
if (cmd == NetworkEvent.Type.Data) {
NativeArray<byte> data = new NativeArray<byte>(stream.Length, Allocator.Temp);
stream.ReadBytes(data);
Stats.BytesReceived += (ulong) stream.Length;
Delegate?.OnMessageReceived(k_ServerClientID, data);
} else if (cmd == NetworkEvent.Type.Disconnect) {
m_IsClient = false;
m_ClientConnection = default;
Delegate?.OnClientDisconnected(LocalClientID);
s_Logger.Info("Disconnected from server");
} else if (cmd == NetworkEvent.Type.Connect) {
s_Logger.Info("Client connected");
Delegate?.OnClientConnected(LocalClientID);
}
}
}
//
// @MARK: INetworkManager
//
public bool IsServer() {
return m_IsServer;
}
public bool StartHost() {
if (IsServer()) {
s_Logger.Error("Server already started");
return false;
}
Stats.Reset();
NetworkEndpoint endpoint = NetworkEndpoint.AnyIpv4.WithPort(7777);
if (m_Driver.Bind(endpoint) != 0) {
s_Logger.Error($"Failed to bind to port {Port}");
return false;
}
m_Driver.Listen();
m_ClientConnection = m_Driver.Connect(NetworkEndpoint.LoopbackIpv4.WithPort(Port));
if (!m_ClientConnection.IsCreated) {
s_Logger.Error($"Failed to create client connection on port {Port}");
return false;
}
LocalClientID = k_ServerClientID;
m_ServerConnections.Add(LocalClientID, m_ClientConnection);
m_IsServer = true;
m_IsClient = true;
Delegate?.OnServerStarted();
Delegate?.OnClientConnected(LocalClientID);
return true;
}
public void StopHost() { throw new NotImplementedException(); }
public bool IsClient() {
return m_IsClient;
}
public bool StartClient() {
if (IsClient()) {
s_Logger.Error("Client already started");
return false;
}
Stats.Reset();
NetworkEndpoint endpoint = NetworkEndpoint.LoopbackIpv4.WithPort(Port);
m_ClientConnection = m_Driver.Connect(endpoint);
if (!m_ClientConnection.IsCreated) {
s_Logger.Error($"Failed to connect to server on port {Port}");
return false;
}
m_IsClient = true;
Delegate?.OnClientStarted();
return true;
}
public void Disconnect() { throw new NotImplementedException(); }
public void Send(ulong clientID, NativeSlice<byte> data, SendMode mode) {
unsafe {
void* ptr = data.GetUnsafePtr();
Send(clientID, (byte*)ptr, data.Length, mode);
}
}
public unsafe void Send(ulong clientID, byte* data, int length, SendMode mode) {
if (IsServer()) {
if (clientID == LocalClientID) {
NativeArray<byte> messageData = new NativeArray<byte>(length, Allocator.Temp);
UnsafeUtility.MemCpy(messageData.GetUnsafePtr(), data, length);
Delegate?.OnMessageReceived(clientID, messageData);
return;
}
if (m_ServerConnections.TryGetValue(clientID, out NetworkConnection value)) {
if (!value.IsCreated) {
s_Logger.Error($"Client {clientID} connection is not created. Cannot send message.");
return;
}
m_Driver.BeginSend(value, out DataStreamWriter writer);
writer.WriteBytesUnsafe(data, length);
m_Driver.EndSend(writer);
if (mode == SendMode.Reliable) {
Stats.ReliableBytesSent += (ulong) length;
} else if (mode == SendMode.Unreliable){
Stats.UnreliableBytesSent += (ulong) length;
}
} else {
s_Logger.Error($"Client {clientID} not found. Cannot send message.");
}
} else if (IsClient()) {
if (clientID != k_ServerClientID) {
s_Logger.Error("Client ID mismatch. Cannot send message to another client from client context.");
return;
}
if (!m_ClientConnection.IsCreated) {
s_Logger.Error("Client connection is not created. Cannot send message.");
return;
}
if (m_Driver.BeginSend(m_ClientConnection, out DataStreamWriter writer, length) == 0) {
writer.WriteBytesUnsafe(data, length);
if (m_Driver.EndSend(writer) < 0) {
s_Logger.Error("Failed to send message to server.");
}
if (mode == SendMode.Reliable) {
Stats.ReliableBytesSent += (ulong) length;
} else if (mode == SendMode.Unreliable){
Stats.UnreliableBytesSent += (ulong) length;
}
} else {
s_Logger.Error($"Failed to begin sending message to server with length {length}.");
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f2b23e503c2946ef9b2d631cef89facb
timeCreated: 1753326254