Files
RebootKit/Runtime/Engine/Code/Network/NetworkSystem.cs
2025-07-30 05:51:39 +02:00

933 lines
36 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Cysharp.Threading.Tasks;
using RebootKit.Engine.Extensions;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Simulation;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Assertions;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootKit.Engine.Network {
enum NetworkClientSyncState {
NotReady,
Synchronizing,
Ready
}
class NetworkClientState : IDisposable {
public NetworkSystem NetworkSystem;
public ulong ClientID;
public NetworkClientSyncState SyncState;
public NetworkMessageBuffer ActorsEvents;
public NetworkMessageBuffer ActorsCoreStatesUpdates;
public NetworkMessageBuffer ActorsStatesUpdates;
public NetworkMessageBuffer ActorsTransformUpdates;
public Queue<NetworkMessageBuffer> ReliableMessagesQueue = new Queue<NetworkMessageBuffer>(64);
public Queue<NetworkMessageBuffer> UnreliableMessagesQueue = new Queue<NetworkMessageBuffer>(64);
public bool IsReady {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get {
return SyncState == NetworkClientSyncState.Ready;
}
}
public bool IsServer { get; private set; }
public NetworkClientState(NetworkSystem networkSystem, ulong clientID, bool isServer) {
NetworkSystem = networkSystem;
ClientID = clientID;
SyncState = NetworkClientSyncState.NotReady;
IsServer = isServer;
ResetMessageBuffers();
}
public void Dispose() {
while (ReliableMessagesQueue.Count > 0) {
NetworkMessageBuffer message = ReliableMessagesQueue.Dequeue();
NetworkSystem.ReturnMessageBufferToFreeList(message);
}
while (UnreliableMessagesQueue.Count > 0) {
NetworkMessageBuffer message = UnreliableMessagesQueue.Dequeue();
NetworkSystem.ReturnMessageBufferToFreeList(message);
}
if (ActorsEvents != null) {
NetworkSystem.ReturnMessageBufferToFreeList(ActorsEvents);
}
if (ActorsCoreStatesUpdates != null) {
NetworkSystem.ReturnMessageBufferToFreeList(ActorsCoreStatesUpdates);
}
if (ActorsStatesUpdates != null) {
NetworkSystem.ReturnMessageBufferToFreeList(ActorsStatesUpdates);
}
if (ActorsTransformUpdates != null) {
NetworkSystem.ReturnMessageBufferToFreeList(ActorsTransformUpdates);
}
ActorsEvents = null;
ActorsCoreStatesUpdates = null;
ActorsStatesUpdates = null;
ActorsTransformUpdates = null;
}
public void ResetMessageBuffers() {
if (ActorsEvents == null) {
ActorsEvents = NetworkSystem.GetFreeMessageBuffer();
}
ActorsEvents.Reset();
ActorsEvents.WriteHeader(NetworkMessageType.ActorsEventsList, RR.NetworkProtocolVersion);
ActorsEvents.Writer.Write((byte) 0);
if (ActorsCoreStatesUpdates == null) {
ActorsCoreStatesUpdates = NetworkSystem.GetFreeMessageBuffer();
}
ActorsCoreStatesUpdates.Reset();
ActorsCoreStatesUpdates.WriteHeader(NetworkMessageType.ActorsCoreStatesUpdateList,
RR.NetworkProtocolVersion);
ActorsCoreStatesUpdates.Writer.Write((byte) 0);
if (ActorsStatesUpdates == null) {
ActorsStatesUpdates = NetworkSystem.GetFreeMessageBuffer();
}
ActorsStatesUpdates.Reset();
ActorsStatesUpdates.WriteHeader(NetworkMessageType.ActorsStatesUpdateList, RR.NetworkProtocolVersion);
ActorsStatesUpdates.Writer.Write((byte) 0);
if (ActorsTransformUpdates == null) {
ActorsTransformUpdates = NetworkSystem.GetFreeMessageBuffer();
}
ActorsTransformUpdates.Reset();
ActorsTransformUpdates.WriteHeader(NetworkMessageType.ActorsTransformUpdateList, RR.NetworkProtocolVersion);
ActorsTransformUpdates.Writer.Write((byte) 0);
}
public void ScheduleMessages() {
if (ActorsEvents.GetMessageContentSize() > sizeof(byte)) {
ReliableMessagesQueue.Enqueue(ActorsEvents);
ActorsEvents = null;
}
if (ActorsCoreStatesUpdates.GetMessageContentSize() > sizeof(byte)) {
ReliableMessagesQueue.Enqueue(ActorsCoreStatesUpdates);
ActorsCoreStatesUpdates = null;
}
if (ActorsStatesUpdates.GetMessageContentSize() > sizeof(byte)) {
ReliableMessagesQueue.Enqueue(ActorsStatesUpdates);
ActorsStatesUpdates = null;
}
if (ActorsTransformUpdates.GetMessageContentSize() > sizeof(byte)) {
UnreliableMessagesQueue.Enqueue(ActorsTransformUpdates);
ActorsTransformUpdates = null;
}
ResetMessageBuffers();
}
}
public class NetworkSystem : INetworkManagerDelegate, IDisposable {
[ConfigVar("sv.tick_rate", 30, "Server tick rate in Hz", CVarFlags.Server)]
public static ConfigVar TickRate;
static readonly Logger s_Logger = new Logger(nameof(NetworkSystem));
public ActorsManager Actors { get; private set; }
internal readonly Dictionary<ulong, NetworkClientState> Clients = new Dictionary<ulong, NetworkClientState>();
NetworkMessageBuffer m_ActorsCommandsMessage;
List<NetworkMessageBuffer> m_MessagesFreeList = new List<NetworkMessageBuffer>();
FixedString64Bytes m_WorldID = new FixedString64Bytes("");
public FixedString64Bytes WorldID {
get {
return m_WorldID;
}
}
bool m_IsChangingWorld = false;
CancellationTokenSource m_WorldChangeCancellationTokenSource = new CancellationTokenSource();
float m_TickTimer;
public INetworkManager Manager { get; private set; }
public ulong TickCount { get; private set; }
public event Action<ulong> ServerTick = delegate { };
public ulong LocalClientID {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get {
return Manager.LocalClientID;
}
}
public int LastTickPacketsSentCount { get; private set; }
public NetworkSystem(INetworkManager manager) {
Manager = manager;
Manager.Delegate = this;
Actors = new ActorsManager(this);
m_ActorsCommandsMessage = new NetworkMessageBuffer(Allocator.Persistent);
m_ActorsCommandsMessage.WriteHeader(NetworkMessageType.ActorsCommandsList, RR.EngineConfig.protocolVersion);
m_ActorsCommandsMessage.Writer.Write((byte) 0);
}
public void Dispose() {
foreach (NetworkClientState clientState in Clients.Values) {
clientState.Dispose();
}
Clients.Clear();
Manager.Dispose();
Manager = null;
m_ActorsCommandsMessage.Dispose();
m_ActorsCommandsMessage = null;
foreach (NetworkMessageBuffer networkMessageBuffer in m_MessagesFreeList) {
networkMessageBuffer.Dispose();
}
m_MessagesFreeList.Clear();
}
public void Tick(float deltaTime) {
Manager.Tick();
if (!Manager.IsServer() && !Manager.IsClient()) {
return;
}
Actors.Tick(deltaTime);
float tickDeltaTime = 1.0f / TickRate.IndexValue;
m_TickTimer += deltaTime;
while (m_TickTimer >= tickDeltaTime) {
m_TickTimer -= tickDeltaTime;
LastTickPacketsSentCount = 0;
if (RR.IsServer()) {
Actors.ServerTick(tickDeltaTime);
ServerTick?.Invoke(TickCount);
TickCount++;
FlushServerPackets();
}
if (RR.IsClient()) {
if (!RR.IsServer()) {
TickCount++;
}
FlushClientPackets();
}
}
}
//
// @MARK: INetworkManagerDelegate
//
public void OnServerStarted() {
TickCount = 0;
RR.OnServerStarted();
}
public void OnServerStopped() {
RR.OnServerStopped();
}
public void OnClientStarted() {
RR.OnClientStarted();
}
public void OnClientStopped() {
RR.OnClientStopped();
}
public void OnClientConnected(ulong clientID) {
s_Logger.Info($"OnClientConnected: {clientID}");
if (RR.IsServer()) {
NetworkClientState newClientState = new NetworkClientState(this,
clientID,
clientID == Manager.LocalClientID);
Clients.Add(clientID, newClientState);
if (!WorldID.IsEmpty && !newClientState.IsServer) {
s_Logger.Info($"Synchronizing world load for client {clientID} with world ID '{WorldID}'");
SendSynchronizeGameState(clientID);
}
}
if (RR.GameInstance != null) {
RR.GameInstance.OnClientConnected(clientID);
}
}
public void OnClientDisconnected(ulong clientID) {
s_Logger.Info($"OnClientDisconnected: {clientID}");
if (RR.IsServer()) {
Clients.Remove(clientID);
}
if (RR.GameInstance != null) {
RR.GameInstance.OnClientDisconnected(clientID);
}
}
public void OnMessageReceived(ulong senderID, NativeArray<byte> data) {
OnReceivedNetworkMessage(senderID, data);
}
//
// @MARK: Server API
//
public void SetCurrentWorld(string worldID) {
if (!Manager.IsServer()) {
s_Logger.Error("Only server can set the current world.");
return;
}
if (m_IsChangingWorld) {
s_Logger.Error($"Already changing world to '{WorldID}'. Please wait until the current world change is complete.");
return;
}
WorldConfigAsset worldConfigAsset = RR.GetWorldConfigAsset(worldID);
if (worldConfigAsset is null) {
s_Logger.Error($"Failed to set current world: World config asset for '{worldID}' not found.");
return;
}
m_WorldID = worldID;
foreach ((ulong _, NetworkClientState clientState) in Clients.ToList()) {
clientState.SyncState = NetworkClientSyncState.Synchronizing;
}
m_WorldChangeCancellationTokenSource.Cancel();
m_WorldChangeCancellationTokenSource.Dispose();
m_WorldChangeCancellationTokenSource = new CancellationTokenSource();
ServerLoadWorldAsync(worldConfigAsset, m_WorldChangeCancellationTokenSource.Token).Forget();
}
async UniTask ServerLoadWorldAsync(WorldConfigAsset asset, CancellationToken cancellationToken) {
s_Logger.Info($"ServerLoadWorldAsync: {asset.Config.name}");
m_IsChangingWorld = true;
RR.World.Unload();
RR.CloseMainMenu();
await RR.World.LoadAsync(asset.Config, cancellationToken);
m_IsChangingWorld = false;
await UniTask.Yield(cancellationToken);
Actors.AssignActorsIDs();
if (!TryGetClientState(LocalClientID, out NetworkClientState localClientState)) {
s_Logger.Error($"Local client state not found for client ID {LocalClientID.ToString()}.");
RR.Disconnect();
return;
}
localClientState.SyncState = NetworkClientSyncState.Ready;
RR.GameInstance.PlayerBecameReady(localClientState.ClientID);
foreach (ulong clientID in Clients.Keys) {
if (clientID == LocalClientID) {
continue;
}
SendSynchronizeGameState(clientID);
}
}
void SendSynchronizeGameState(ulong clientID) {
if (!RR.IsServer()) {
s_Logger.Error("Only server can send synchronize game state.");
return;
}
using NetworkMessageBuffer message = new NetworkMessageBuffer(Allocator.Temp);
message.WriteHeader(NetworkMessageType.SynchronizeGameState, RR.EngineConfig.protocolVersion);
message.Writer.Write(WorldID);
Actors.WriteInSceneActorsStates(message.Writer);
unsafe {
Manager.Send(clientID,
(byte*) message.GetDataSlice().GetUnsafePtr(),
message.Writer.Position,
SendMode.Reliable);
}
}
//
// @MARK: Internal
//
internal bool TryGetClientState(ulong clientID, out NetworkClientState clientState) {
return Clients.TryGetValue(clientID, out clientState);
}
internal void ClientSynchronized(ulong clientID) {
if (TryGetClientState(clientID, out NetworkClientState state)) {
state.SyncState = NetworkClientSyncState.Ready;
s_Logger.Info($"Client synchronized: {clientID.ToString()}");
Actors.SpawnDynamicActorsForClient(clientID);
RR.GameInstance.PlayerBecameReady(clientID);
} else {
s_Logger.Error($"Client state for {clientID} not found.");
}
}
internal int GetReadyClientsCount() {
int count = 0;
foreach (NetworkClientState clientState in Clients.Values) {
if (clientState.IsReady) {
count++;
}
}
return count;
}
//
// @MARK: Network packets
//
internal void SendPossessedActor(ulong clientID, ushort actorID) {
if (!RR.IsServer()) {
s_Logger.Error("Only server can send possessed actor message.");
return;
}
if (TryGetClientState(clientID, out NetworkClientState clientState)) {
using NetworkMessageBuffer messageBuffer = new NetworkMessageBuffer(Allocator.Temp);
messageBuffer.WriteHeader(NetworkMessageType.PossessActor, RR.EngineConfig.protocolVersion);
messageBuffer.Writer.Write(actorID);
LastTickPacketsSentCount += 1;
Manager.Send(clientID, messageBuffer.GetDataSlice(), SendMode.Reliable);
return;
}
s_Logger.Error($"Failed to find client state for client ID {clientID}. Cannot send possess actor message.");
}
internal bool WriteActorState(ulong clientID, ushort actorID, IActorData actorData) {
if (!RR.IsServer()) {
s_Logger.Error("WriteActorState can only be called on the server.");
return false;
}
Assert.IsTrue(actorData.GetMaxBytes() <= byte.MaxValue, "Actor data must not exceed 255 bytes.");
if (TryGetClientState(clientID, out NetworkClientState clientState)) {
NetworkMessageBuffer messageBuffer = clientState.ActorsStatesUpdates;
int dataSizeToFit = actorData.GetMaxBytes() + sizeof(ushort) + sizeof(byte);
if (!messageBuffer.Writer.WillFit(dataSizeToFit)) {
return false;
}
// Increment count of entities in the message
int savedPosition = messageBuffer.Writer.Position;
using NetworkBufferReader reader = new NetworkBufferReader(messageBuffer.GetDataSlice(),
NetworkMessageHeader.k_HeaderSize);
reader.Read(out byte count);
if (count >= byte.MaxValue) {
return false;
}
messageBuffer.Writer.Position = NetworkMessageHeader.k_HeaderSize;
messageBuffer.Writer.Write((byte) (count + 1));
messageBuffer.Writer.Position = savedPosition;
// Actual data writing
messageBuffer.Writer.Write(actorID);
messageBuffer.Writer.Write((byte) actorData.GetMaxBytes());
actorData.Serialize(messageBuffer.Writer);
return true;
}
return false;
}
// @NOTE: We are assuming that message buffer is already initialized with header and count of entities.
static bool CanFitNextEntityInMessage(NetworkMessageBuffer messageBuffer, int dataSizeToFit) {
if (!messageBuffer.Writer.WillFit(dataSizeToFit)) {
return false;
}
// Increment count of entities in the message
using NetworkBufferReader reader = new NetworkBufferReader(messageBuffer.GetDataSlice(),
NetworkMessageHeader.k_HeaderSize);
reader.Read(out byte count);
if (count >= byte.MaxValue) {
return false;
}
return true;
}
internal bool WriteActorTransformState(ulong clientID, ActorTransformSyncData transformData) {
if (!RR.IsServer()) {
s_Logger.Error("WriteActorTransformState can only be called on the server.");
return false;
}
if (TryGetClientState(clientID, out NetworkClientState clientState)) {
int dataSizeToFit = transformData.GetMaxBytes();
NetworkMessageBuffer messageBuffer = clientState.ActorsTransformUpdates;
if (!CanFitNextEntityInMessage(messageBuffer, dataSizeToFit)) {
return false;
}
// Increment count of entities in the message
int savedPosition = messageBuffer.Writer.Position;
using NetworkBufferReader reader = new NetworkBufferReader(messageBuffer.GetDataSlice(),
NetworkMessageHeader.k_HeaderSize);
reader.Read(out byte count);
if (count >= byte.MaxValue) {
return false;
}
messageBuffer.Writer.Position = NetworkMessageHeader.k_HeaderSize;
messageBuffer.Writer.Write((byte) (count + 1));
messageBuffer.Writer.Position = savedPosition;
// Actual data writing
transformData.Serialize(messageBuffer.Writer);
return true;
}
return false;
}
internal bool WriteActorCoreState(ulong clientID, ActorCoreStateSnapshot coreData) {
if (!RR.IsServer()) {
s_Logger.Error("WriteActorCoreState can only be called on the server.");
return false;
}
if (TryGetClientState(clientID, out NetworkClientState clientState)) {
int dataSizeToFit = coreData.GetMaxBytes();
NetworkMessageBuffer messageBuffer = clientState.ActorsCoreStatesUpdates;
if (!messageBuffer.Writer.WillFit(dataSizeToFit)) {
return false;
}
// Increment count of entities in the message
int savedPosition = messageBuffer.Writer.Position;
using NetworkBufferReader reader = new NetworkBufferReader(messageBuffer.GetDataSlice(),
NetworkMessageHeader.k_HeaderSize);
reader.Read(out byte count);
if (count >= byte.MaxValue) {
return false;
}
messageBuffer.Writer.Position = NetworkMessageHeader.k_HeaderSize;
messageBuffer.Writer.Write((byte) (count + 1));
messageBuffer.Writer.Position = savedPosition;
// Actual data writing
coreData.Serialize(messageBuffer.Writer);
return true;
}
return false;
}
internal bool SendSpawnActor(ulong clientID,
FixedString64Bytes assetGUID,
ActorCoreStateSnapshot coreState,
IActorData actorData) {
if (!RR.IsServer()) {
s_Logger.Error("WriteSpawnActor can only be called on the server.");
return false;
}
if (TryGetClientState(clientID, out NetworkClientState clientState)) {
int dataSizeToFit = sizeof(byte) * 64 + // assetGUID
coreState.GetMaxBytes() +
sizeof(byte) + // actor data size
actorData.GetMaxBytes();
using NetworkMessageBuffer messageBuffer = new NetworkMessageBuffer(Allocator.Temp);
messageBuffer.WriteHeader(NetworkMessageType.SpawnActor, RR.NetworkProtocolVersion);
if (!messageBuffer.Writer.WillFit(dataSizeToFit)) {
s_Logger.Error($"WriteSpawnActor failed: message buffer for client {clientID} is full. " +
$"Data size to fit: {dataSizeToFit}");
return false;
}
messageBuffer.Writer.Write(assetGUID);
coreState.Serialize(messageBuffer.Writer);
messageBuffer.Writer.Write((byte) actorData.GetMaxBytes());
actorData.Serialize(messageBuffer.Writer);
LastTickPacketsSentCount += 1;
Manager.Send(clientID, messageBuffer.GetDataSlice(), SendMode.Reliable);
return true;
}
return false;
}
internal bool WriteActorEvent(ulong clientID,
ActorEvent actorEvent) {
if (!RR.IsServer()) {
s_Logger.Error("WriteActorEvent can only be called on the server.");
return false;
}
if (TryGetClientState(clientID, out NetworkClientState clientState)) {
int dataSizeToFit = actorEvent.GetMaxBytes();
NetworkMessageBuffer messageBuffer = clientState.ActorsEvents;
if (!messageBuffer.Writer.WillFit(dataSizeToFit)) {
return false;
}
// Increment count of entities in the message
int savedPosition = messageBuffer.Writer.Position;
using NetworkBufferReader reader = new NetworkBufferReader(messageBuffer.GetDataSlice(),
NetworkMessageHeader.k_HeaderSize);
reader.Read(out byte count);
if (count >= byte.MaxValue) {
return false;
}
messageBuffer.Writer.Position = NetworkMessageHeader.k_HeaderSize;
messageBuffer.Writer.Write((byte) (count + 1));
messageBuffer.Writer.Position = savedPosition;
// Actual data writing
actorEvent.Serialize(messageBuffer.Writer);
return true;
}
return false;
}
// @TODO: When buffer is overloaded we might want to flush it immediately instead of returning false.
internal bool WriteActorCommand(ActorCommand actorCommand) {
if (actorCommand.ActorID == 0) {
s_Logger.Error("Trying to write an actor command with ActorID 0. " +
"This is likely a bug in the code that generates actor commands.");
}
NetworkMessageBuffer messageBuffer = m_ActorsCommandsMessage;
if (!messageBuffer.Writer.WillFit(actorCommand.GetMaxBytes())) {
return false;
}
// Increment count of entities in the message
using NetworkBufferReader reader = new NetworkBufferReader(messageBuffer.GetDataSlice(),
NetworkMessageHeader.k_HeaderSize);
reader.Read(out byte count);
if (count >= byte.MaxValue) {
return false;
}
int savedPosition = messageBuffer.Writer.Position;
messageBuffer.Writer.Position = NetworkMessageHeader.k_HeaderSize;
messageBuffer.Writer.Write((byte) (count + 1));
messageBuffer.Writer.Position = savedPosition;
// Actual data writing
actorCommand.Serialize(messageBuffer.Writer);
return true;
}
void FlushClientPackets() {
if (!RR.IsClient()) {
return;
}
if (m_ActorsCommandsMessage.GetMessageContentSize() > sizeof(byte)) {
if (RR.IsServer()) {
using NativeArray<byte> messageData = m_ActorsCommandsMessage.GetDataCopy(Allocator.Temp);
ResetClientsMessages();
OnReceivedNetworkMessage(Manager.LocalClientID, messageData);
} else {
LastTickPacketsSentCount += 1;
Manager.Send(0, m_ActorsCommandsMessage.GetDataSlice(), SendMode.Reliable);
ResetClientsMessages();
}
}
}
void ResetClientsMessages() {
m_ActorsCommandsMessage.Reset();
m_ActorsCommandsMessage.WriteHeader(NetworkMessageType.ActorsCommandsList, RR.EngineConfig.protocolVersion);
m_ActorsCommandsMessage.Writer.Write((byte) 0);
}
void FlushServerPackets() {
if (!RR.IsServer()) {
return;
}
foreach (NetworkClientState client in RR.Network.Clients.Values) {
bool isClientServer = client.ClientID == Manager.LocalClientID;
client.ScheduleMessages();
if (isClientServer) {
while (client.ReliableMessagesQueue.Count > 0) {
NetworkMessageBuffer message = client.ReliableMessagesQueue.Dequeue();
OnMessageReceived(LocalClientID, message.GetDataCopy(Allocator.Temp));
}
while (client.UnreliableMessagesQueue.Count > 0) {
NetworkMessageBuffer message = client.UnreliableMessagesQueue.Dequeue();
OnMessageReceived(LocalClientID, message.GetDataCopy(Allocator.Temp));
}
} else {
while (client.ReliableMessagesQueue.Count > 0) {
NetworkMessageBuffer message = client.ReliableMessagesQueue.Dequeue();
LastTickPacketsSentCount += 1;
Manager.Send(client.ClientID, message.GetDataSlice(), SendMode.Reliable);
}
while (client.UnreliableMessagesQueue.Count > 0) {
NetworkMessageBuffer message = client.UnreliableMessagesQueue.Dequeue();
LastTickPacketsSentCount += 1;
Manager.Send(client.ClientID, message.GetDataSlice(), SendMode.Unreliable);
}
}
foreach (NetworkMessageBuffer messageToFree in client.ReliableMessagesQueue) {
ReturnMessageBufferToFreeList(messageToFree);
}
client.ReliableMessagesQueue.Clear();
foreach (NetworkMessageBuffer messageToFree in client.UnreliableMessagesQueue) {
ReturnMessageBufferToFreeList(messageToFree);
}
client.UnreliableMessagesQueue.Clear();
}
}
void OnReceivedNetworkMessage(ulong senderID, NativeArray<byte> data) {
if (!data.IsCreated) {
s_Logger.Error("Received empty network message data.");
return;
}
NetworkMessageHeader messageHeader = new NetworkMessageHeader();
using (NetworkBufferReader reader = new NetworkBufferReader(data)) {
messageHeader.Deserialize(reader);
}
Assert.IsTrue(messageHeader.MagicNumber == RConsts.k_NetworkMessageMagic,
"Received packet with invalid magic number.");
NativeSlice<byte> messageContent = data.Slice(NetworkMessageHeader.k_HeaderSize);
switch (messageHeader.MessageType) {
// @MARK: Server to client messages
case NetworkMessageType.SpawnActor: {
using NetworkBufferReader contentReader = new NetworkBufferReader(messageContent);
contentReader.Read(out FixedString64Bytes actorAssetGUID);
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
coreState.Deserialize(contentReader);
contentReader.Read(out byte dataSize);
contentReader.Read(out NativeSlice<byte> actorDataContent, dataSize);
Actors.ProcessSpawnActor(actorAssetGUID, coreState, actorDataContent);
break;
}
case NetworkMessageType.PossessActor: {
using NetworkBufferReader contentReader = new NetworkBufferReader(messageContent);
contentReader.Read(out ushort actorID);
RR.GameInstance.LocalPlayerController.SetPossessedActor(actorID);
break;
}
case NetworkMessageType.ActorsEventsList: {
using NetworkBufferReader contentReader = new NetworkBufferReader(messageContent);
contentReader.Read(out byte count);
for (int i = 0; i < count; ++i) {
ActorEvent actorEvent = new ActorEvent();
actorEvent.Deserialize(contentReader);
Actors.ProcessActorEvent(actorEvent);
}
break;
}
case NetworkMessageType.ActorsCoreStatesUpdateList: {
using NetworkBufferReader contentReader = new NetworkBufferReader(messageContent);
contentReader.Read(out byte count);
for (int i = 0; i < count; ++i) {
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
coreState.Deserialize(contentReader);
Actors.ProcessActorCoreState(coreState);
}
break;
}
case NetworkMessageType.ActorsStatesUpdateList: {
using NetworkBufferReader contentReader = new NetworkBufferReader(messageContent);
contentReader.Read(out byte count);
for (int i = 0; i < count; ++i) {
contentReader.Read(out ushort actorID);
contentReader.Read(out byte dataSize);
contentReader.Read(out NativeSlice<byte> actorDataContent, dataSize);
Actors.ProcessActorState(actorID, actorDataContent);
}
break;
}
case NetworkMessageType.ActorsTransformUpdateList: {
using NetworkBufferReader contentReader = new NetworkBufferReader(messageContent);
contentReader.Read(out byte count);
for (int i = 0; i < count; ++i) {
ActorTransformSyncData transformData = new ActorTransformSyncData();
transformData.Deserialize(contentReader);
Actors.ProcessActorTransformState(transformData);
}
break;
}
case NetworkMessageType.SynchronizeGameState: {
ProcessSynchronizeGameStateMessage(messageContent);
break;
}
// @MARK: Client to server messages
case NetworkMessageType.ClientSynchronizedGameState: {
s_Logger.Info("On Player Synchronized Game State: " + senderID);
ClientSynchronized(senderID);
break;
}
case NetworkMessageType.ActorsCommandsList: {
using NetworkBufferReader contentReader = new NetworkBufferReader(messageContent);
contentReader.Read(out byte count);
for (int i = 0; i < count; ++i) {
ActorCommand actorCommand = new ActorCommand();
actorCommand.Deserialize(contentReader);
Actors.ProcessActorCommand(senderID, actorCommand);
}
break;
}
default:
s_Logger.Error($"Received packet with unsupported message type: {messageHeader.MessageType.ToString()}");
break;
}
}
void ProcessSynchronizeGameStateMessage(NativeSlice<byte> data) {
if (RR.IsServer()) {
s_Logger.Error("Server should not receive SynchronizeGameState message.");
return;
}
// @TODO: Handle cancellation token properly in this method.
ProcessSynchronizeGameStateMessageAsync(data, CancellationToken.None).Forget();
}
async UniTask ProcessSynchronizeGameStateMessageAsync(NativeSlice<byte> data,
CancellationToken cancellationToken) {
using NativeArray<byte> dataArray = new NativeArray<byte>(data.Length, Allocator.Persistent);
data.CopyTo(dataArray);
using NetworkBufferReader reader = new NetworkBufferReader(dataArray);
reader.Read(out m_WorldID);
WorldConfigAsset worldConfigAsset = RR.GetWorldConfigAsset(m_WorldID.ToString());
if (worldConfigAsset is null) {
s_Logger.Error($"Failed to synchronize game state: World config asset for '{m_WorldID}' not found.");
RR.Disconnect();
return;
}
RR.World.Unload();
RR.CloseMainMenu();
await RR.World.LoadAsync(worldConfigAsset.Config, cancellationToken);
if (!Actors.ReadInSceneActorsStates(reader)) {
s_Logger.Error("Failed to read in-scene actors states during game state synchronization.");
RR.Disconnect();
return;
}
NotifyServerClientSynchronizedGameState();
}
void NotifyServerClientSynchronizedGameState() {
s_Logger.Info("Sending ClientSynchronizedGameState message to server");
using NetworkMessageBuffer message = new NetworkMessageBuffer(Allocator.Temp);
message.WriteHeader(NetworkMessageType.ClientSynchronizedGameState, RR.EngineConfig.protocolVersion);
message.Writer.Write(m_WorldID);
Manager.Send(0, message.GetDataSlice(), SendMode.Reliable);
}
//
// @MARK: Messages free list
//
internal NetworkMessageBuffer GetFreeMessageBuffer() {
if (m_MessagesFreeList.Count > 0) {
int index = m_MessagesFreeList.Count - 1;
NetworkMessageBuffer messageBuffer = m_MessagesFreeList[index];
m_MessagesFreeList.RemoveAt(index);
return messageBuffer;
}
return new NetworkMessageBuffer(Allocator.Persistent);
}
internal void ReturnMessageBufferToFreeList(NetworkMessageBuffer messageBuffer) {
if (m_MessagesFreeList.Contains(messageBuffer)) {
s_Logger.Error("Trying to return a message buffer that is already in the free list.");
return;
}
m_MessagesFreeList.Add(messageBuffer);
}
}
}