933 lines
36 KiB
C#
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);
|
|
}
|
|
}
|
|
} |