optimizing network stuff
This commit is contained in:
@@ -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)) {
|
||||
|
||||
62
Runtime/Engine/Code/Network/INetworkManager.cs
Normal file
62
Runtime/Engine/Code/Network/INetworkManager.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
3
Runtime/Engine/Code/Network/INetworkManager.cs.meta
Normal file
3
Runtime/Engine/Code/Network/INetworkManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26612cc58cd249cf8d567501e253eab1
|
||||
timeCreated: 1753138601
|
||||
@@ -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;
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
Runtime/Engine/Code/Network/NetworkMessageBuffer.cs
Normal file
109
Runtime/Engine/Code/Network/NetworkMessageBuffer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
3
Runtime/Engine/Code/Network/NetworkMessageBuffer.cs.meta
Normal file
3
Runtime/Engine/Code/Network/NetworkMessageBuffer.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9d2fa991d31446db7fe1a4fef441a1d
|
||||
timeCreated: 1753152667
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a122573c79c4b3e9ef3bc2da3b09faa
|
||||
timeCreated: 1752855419
|
||||
@@ -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
@@ -1,6 +0,0 @@
|
||||
using Unity.Netcode;
|
||||
|
||||
namespace RebootKit.Engine.Network {
|
||||
public abstract class NetworkWorldController : NetworkBehaviour {
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edd521a69a6f4e43b97ec258adf499a6
|
||||
timeCreated: 1751377120
|
||||
291
Runtime/Engine/Code/Network/UnityNetworkManager.cs
Normal file
291
Runtime/Engine/Code/Network/UnityNetworkManager.cs
Normal 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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Engine/Code/Network/UnityNetworkManager.cs.meta
Normal file
3
Runtime/Engine/Code/Network/UnityNetworkManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2b23e503c2946ef9b2d631cef89facb
|
||||
timeCreated: 1753326254
|
||||
Reference in New Issue
Block a user