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 ReliableMessagesQueue = new Queue(64); public Queue UnreliableMessagesQueue = new Queue(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 Clients = new Dictionary(); NetworkMessageBuffer m_ActorsCommandsMessage; List m_MessagesFreeList = new List(); 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 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 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 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 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 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 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 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 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 data, CancellationToken cancellationToken) { using NativeArray dataArray = new NativeArray(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); } } }