diff --git a/Runtime/Engine/Code/Development/DebugOverlayView.cs b/Runtime/Engine/Code/Development/DebugOverlayView.cs index eabd754..fc6cdd7 100644 --- a/Runtime/Engine/Code/Development/DebugOverlayView.cs +++ b/Runtime/Engine/Code/Development/DebugOverlayView.cs @@ -68,6 +68,7 @@ namespace RebootKit.Engine.Development { m_StringBuilder.Append($" | WorldID: {network.WorldID.ToString()}"); m_StringBuilder.Append($" | Clients: {network.Clients.Count.ToString()}"); m_StringBuilder.Append($" | ReadyClientsCount: {network.GetReadyClientsCount().ToString()}"); + m_StringBuilder.Append($" | Last Tick Packets Sent: {network.LastTickPacketsSentCount.ToString()}"); m_StringBuilder.AppendLine(); if (network.TryGetClientState(network.LocalClientID, out NetworkClientState clientState)) { diff --git a/Runtime/Engine/Code/Network/ISerializableEntity.cs b/Runtime/Engine/Code/Network/ISerializableEntity.cs index a79a5f9..267d639 100644 --- a/Runtime/Engine/Code/Network/ISerializableEntity.cs +++ b/Runtime/Engine/Code/Network/ISerializableEntity.cs @@ -1,8 +1,9 @@ namespace RebootKit.Engine.Network { public interface ISerializableEntity { + int GetMaxBytes(); + void Serialize(NetworkBufferWriter writer); void Deserialize(NetworkBufferReader reader); - int GetMaxBytes(); } } \ No newline at end of file diff --git a/Runtime/Engine/Code/Network/NetworkPacketQueue.cs b/Runtime/Engine/Code/Network/NetworkPacketQueue.cs index 7a0eba4..68784a3 100644 --- a/Runtime/Engine/Code/Network/NetworkPacketQueue.cs +++ b/Runtime/Engine/Code/Network/NetworkPacketQueue.cs @@ -92,15 +92,15 @@ namespace RebootKit.Engine.Network { struct NetworkDataHeader : ISerializableEntity { public NetworkDataType Type; public ulong ActorID; - public byte CommandID; - public byte EventID; 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(CommandID); - writer.Write(EventID); writer.Write(DataSize); } @@ -108,14 +108,8 @@ namespace RebootKit.Engine.Network { reader.Read(out byte typeByte); Type = (NetworkDataType) typeByte; reader.Read(out ActorID); - reader.Read(out CommandID); - reader.Read(out EventID); reader.Read(out DataSize); } - - public int GetMaxBytes() { - return sizeof(ulong) + sizeof(byte) * 3 + sizeof(int); - } } class NetworkPacketQueue : IDisposable { @@ -126,7 +120,7 @@ namespace RebootKit.Engine.Network { internal readonly List NetworkPackets = new List(); - public NetworkPacketQueue(int packetMaxSize, ushort version = 2137) { + 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."); @@ -247,6 +241,34 @@ namespace RebootKit.Engine.Network { 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)) { diff --git a/Runtime/Engine/Code/Network/NetworkSystem.cs b/Runtime/Engine/Code/Network/NetworkSystem.cs index f86d63e..083de56 100644 --- a/Runtime/Engine/Code/Network/NetworkSystem.cs +++ b/Runtime/Engine/Code/Network/NetworkSystem.cs @@ -48,7 +48,8 @@ namespace RebootKit.Engine.Network { struct NetworkPacketTarget { public enum Type { AllClients, - Single + Single, + Server } public Type TargetType; @@ -67,6 +68,13 @@ namespace RebootKit.Engine.Network { ClientID = clientID }; } + + public static NetworkPacketTarget Server() { + return new NetworkPacketTarget { + TargetType = Type.Server, + ClientID = 0 + }; + } } public class NetworkSystem : NetworkBehaviour { @@ -75,6 +83,9 @@ namespace RebootKit.Engine.Network { static readonly Logger s_Logger = new Logger(nameof(NetworkSystem)); + const int k_ReliablePacketQueueSize = 1024; + const int k_UnreliablePacketQueueSize = 512; + [field: SerializeField] public ActorsManager Actors { get; private set; } internal readonly Dictionary Clients = new Dictionary(); @@ -94,35 +105,49 @@ namespace RebootKit.Engine.Network { } } + public int LastTickPacketsSentCount { get; private set; } + NetworkPacketQueue m_ReliablePacketQueue; NetworkPacketQueue m_UnreliablePacketQueue; + NetworkPacketQueue m_ReliablePacketQueueToServer; + NetworkPacketQueue m_UnreliablePacketQueueToServer; + // // @MARK: Unity callbacks // void Awake() { RR.NetworkSystemInstance = this; - m_ReliablePacketQueue = new NetworkPacketQueue(1024 * 4); - m_UnreliablePacketQueue = new NetworkPacketQueue(1024); + m_ReliablePacketQueue = new NetworkPacketQueue(k_ReliablePacketQueueSize); + m_UnreliablePacketQueue = new NetworkPacketQueue(k_UnreliablePacketQueueSize); + + m_ReliablePacketQueueToServer = new NetworkPacketQueue(k_ReliablePacketQueueSize); + m_UnreliablePacketQueueToServer = new NetworkPacketQueue(k_UnreliablePacketQueueSize); } void Update() { float deltaTime = Time.deltaTime; - float serverDeltaTime = 1.0f / TickRate.IndexValue; + float tickDeltaTime = 1.0f / TickRate.IndexValue; m_TickTimer += deltaTime; - while (m_TickTimer >= serverDeltaTime) { - m_TickTimer -= serverDeltaTime; + while (m_TickTimer >= tickDeltaTime) { + m_TickTimer -= tickDeltaTime; + + LastTickPacketsSentCount = 0; if (RR.IsServer()) { - Actors.ServerTick(serverDeltaTime); + Actors.ServerTick(tickDeltaTime); ServerTick?.Invoke(TickCount); TickCount++; - FlushNetworkPackets(); + FlushServerPackets(); + } + + if (RR.IsClient()) { + FlushClientPackets(); } } } @@ -154,8 +179,8 @@ namespace RebootKit.Engine.Network { NetworkClientState newClientState = new NetworkClientState { ClientID = clientID, SyncState = NetworkClientSyncState.NotReady, - ReliableQueue = new NetworkPacketQueue(1024 * 4), - UnreliableQueue = new NetworkPacketQueue(512) + ReliableQueue = new NetworkPacketQueue(k_ReliablePacketQueueSize), + UnreliableQueue = new NetworkPacketQueue(k_UnreliablePacketQueueSize) }; Clients.Add(clientID, newClientState); @@ -344,6 +369,10 @@ namespace RebootKit.Engine.Network { if (target.TargetType == NetworkPacketTarget.Type.AllClients) { return reliable ? m_ReliablePacketQueue : m_UnreliablePacketQueue; } + + if (target.TargetType == NetworkPacketTarget.Type.Server) { + return reliable ? m_ReliablePacketQueueToServer : m_UnreliablePacketQueueToServer; + } if (target.TargetType == NetworkPacketTarget.Type.Single) { if (TryGetClientState(target.ClientID, out NetworkClientState clientState)) { @@ -359,6 +388,11 @@ namespace RebootKit.Engine.Network { } internal void WriteActorState(NetworkPacketTarget target, ulong actorID, IActorData actorData) { + if (!RR.IsServer()) { + s_Logger.Error("WriteActorState can only be called on the server."); + return; + } + NetworkPacketQueue queue = GetPacketQueue(target, true); queue.WriteActorState(actorID, actorData); } @@ -366,6 +400,11 @@ namespace RebootKit.Engine.Network { internal void WriteActorTransformState(NetworkPacketTarget target, ulong actorID, ActorTransformSyncData transformData) { + if (!RR.IsServer()) { + s_Logger.Error("WriteActorTransformState can only be called on the server."); + return; + } + NetworkPacketQueue queue = GetPacketQueue(target, false); queue.WriteActorTransformState(actorID, transformData); } @@ -373,6 +412,11 @@ namespace RebootKit.Engine.Network { internal void WriteActorCoreState(NetworkPacketTarget target, ulong actorID, ActorCoreStateSnapshot coreData) { + if (!RR.IsServer()) { + s_Logger.Error("WriteActorCoreState can only be called on the server."); + return; + } + NetworkPacketQueue queue = GetPacketQueue(target, true); queue.WriteActorCoreState(actorID, coreData); } @@ -382,6 +426,10 @@ namespace RebootKit.Engine.Network { ulong actorID, ActorCoreStateSnapshot coreState, IActorData actorData) { + if (!RR.IsServer()) { + s_Logger.Error("WriteSpawnActor can only be called on the server."); + return; + } NetworkPacketQueue queue = GetPacketQueue(target, true); queue.WriteSpawnActor(assetGUID, actorID, coreState, actorData); } @@ -390,11 +438,73 @@ namespace RebootKit.Engine.Network { ulong actorID, ActorCoreStateSnapshot coreState, IActorData actorData) { + if (!RR.IsServer()) { + s_Logger.Error("WriteActorSynchronize can only be called on the server."); + return; + } + NetworkPacketQueue queue = GetPacketQueue(target, true); queue.WriteActorSynchronize(actorID, coreState, actorData); } - void FlushNetworkPackets() { + internal void WriteActorEvent(NetworkPacketTarget target, + ActorEvent actorEvent) { + if (!RR.IsServer()) { + s_Logger.Error("WriteActorEvent can only be called on the server."); + return; + } + + NetworkPacketQueue queue = GetPacketQueue(target, true); + queue.WriteActorEvent(actorEvent); + } + + internal void WriteActorCommand(ActorCommand actorCommand) { + NetworkPacketQueue queue = GetPacketQueue(NetworkPacketTarget.Server(), true); + queue.WriteActorCommand(actorCommand); + } + + void FlushClientPackets() { + if (!RR.IsClient()) { + return; + } + + if (RR.IsServer()) { + foreach (NetworkPacket networkPacket in m_ReliablePacketQueueToServer.NetworkPackets) { + if (networkPacket.EntityCount > 0) { + OnReceivedNetworkPacket(networkPacket.Data); + } + } + + foreach (NetworkPacket networkPacket in m_UnreliablePacketQueueToServer.NetworkPackets) { + if (networkPacket.EntityCount > 0) { + OnReceivedNetworkPacket(networkPacket.Data); + } + } + } else { + foreach (NetworkPacket networkPacket in m_ReliablePacketQueueToServer.NetworkPackets) { + if (networkPacket.EntityCount == 0) { + continue; + } + + LastTickPacketsSentCount += 1; + ReliableReceiveNetworkPacketRpc(networkPacket.Data, RpcTarget.Server); + } + + foreach (NetworkPacket networkPacket in m_UnreliablePacketQueueToServer.NetworkPackets) { + if (networkPacket.EntityCount == 0) { + continue; + } + + LastTickPacketsSentCount += 1; + UnreliableReceiveNetworkPacketRpc(networkPacket.Data, RpcTarget.Server); + } + } + + m_ReliablePacketQueueToServer.Clear(); + m_UnreliablePacketQueueToServer.Clear(); + } + + void FlushServerPackets() { if (!RR.IsServer()) { return; } @@ -408,8 +518,9 @@ namespace RebootKit.Engine.Network { if (networkPacket.EntityCount == 0) { continue; } - + if (state.IsReady) { + LastTickPacketsSentCount += 1; ReliableReceiveNetworkPacketRpc(networkPacket.Data, RpcTarget.Single(clientID, RpcTargetUse.Temp)); } @@ -421,12 +532,13 @@ namespace RebootKit.Engine.Network { if (clientID == NetworkManager.Singleton.LocalClientId) { continue; } - + if (networkPacket.EntityCount == 0) { continue; } - + if (state.IsReady) { + LastTickPacketsSentCount += 1; UnreliableReceiveNetworkPacketRpc(networkPacket.Data, RpcTarget.Single(clientID, RpcTargetUse.Temp)); } @@ -440,21 +552,23 @@ namespace RebootKit.Engine.Network { if (clientState.ClientID == NetworkManager.Singleton.LocalClientId) { continue; } - + foreach (NetworkPacket networkPacket in clientState.ReliableQueue.NetworkPackets) { if (networkPacket.EntityCount == 0) { continue; } - + + LastTickPacketsSentCount += 1; ReliableReceiveNetworkPacketRpc(networkPacket.Data, RpcTarget.Single(clientState.ClientID, RpcTargetUse.Temp)); } - + foreach (NetworkPacket networkPacket in clientState.UnreliableQueue.NetworkPackets) { if (networkPacket.EntityCount == 0) { continue; } + LastTickPacketsSentCount += 1; UnreliableReceiveNetworkPacketRpc(networkPacket.Data, RpcTarget.Single(clientState.ClientID, RpcTargetUse.Temp)); } @@ -498,7 +612,7 @@ namespace RebootKit.Engine.Network { if (dataHeader.Type == NetworkDataType.None) { s_Logger.Info("Data of packet with entry with type None:\n" + data.ToHexString()); } - + Assert.IsTrue(dataHeader.Type != NetworkDataType.None, "Received packet with invalid data type."); reader.Read(out NativeArray entityData, dataHeader.DataSize, Allocator.Temp); diff --git a/Runtime/Engine/Code/Simulation/Actor.cs b/Runtime/Engine/Code/Simulation/Actor.cs index 6603bee..e9bb64e 100644 --- a/Runtime/Engine/Code/Simulation/Actor.cs +++ b/Runtime/Engine/Code/Simulation/Actor.cs @@ -23,62 +23,72 @@ namespace RebootKit.Engine.Simulation { } } - public struct ActorCommand : INetworkSerializable { + public struct ActorCommand : ISerializableEntity { public ulong ActorID; public ulong ClientID; public ushort CommandID; public NativeArray Data; - - public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { - serializer.SerializeValue(ref ActorID); - serializer.SerializeValue(ref ClientID); - serializer.SerializeValue(ref CommandID); - - if (serializer.IsWriter) { - bool hasData = Data.IsCreated; - serializer.SerializeValue(ref hasData); - - if (hasData) { - serializer.SerializeValue(ref Data, Allocator.Temp); - } - } else if (serializer.IsReader) { - bool hasData = false; - serializer.SerializeValue(ref hasData); - - if (hasData) { - serializer.SerializeValue(ref Data, Allocator.Temp); - } + + public int GetMaxBytes() { + return sizeof(ulong) + // ActorID + sizeof(ulong) + // ClientID + sizeof(ushort) + // CommandID + sizeof(ushort) + // Data length + sizeof(byte) * Data.Length; // Data + } + + public void Serialize(NetworkBufferWriter writer) { + writer.Write(ActorID); + writer.Write(ClientID); + writer.Write(CommandID); + writer.Write((ushort) Data.Length); + if (Data.IsCreated) { + writer.Write(Data); + } + } + + public void Deserialize(NetworkBufferReader reader) { + reader.Read(out ActorID); + reader.Read(out ClientID); + reader.Read(out CommandID); + reader.Read(out ushort dataLength); + if (dataLength > 0) { + reader.Read(out Data, dataLength, Allocator.Temp); } } } // @NOTE: ActorEvent is used to send events from the server to clients and only clients. // Server should not receive ActorEvents. - public struct ActorEvent : INetworkSerializable { + public struct ActorEvent : ISerializableEntity { public ulong ActorID; - public ulong ClientID; public ushort EventID; public NativeArray Data; - public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { - serializer.SerializeValue(ref ActorID); - serializer.SerializeValue(ref ClientID); - serializer.SerializeValue(ref EventID); + public int GetMaxBytes() { + return sizeof(ulong) + // ActorID + sizeof(ushort) + // EventID + sizeof(ushort) + // Data length + sizeof(byte) * Data.Length; // Data + } - if (serializer.IsWriter) { - bool hasData = Data.IsCreated; - serializer.SerializeValue(ref hasData); + public void Serialize(NetworkBufferWriter writer) { + writer.Write(ActorID); + writer.Write(EventID); - if (hasData) { - serializer.SerializeValue(ref Data, Allocator.Temp); - } - } else if (serializer.IsReader) { - bool hasData = false; - serializer.SerializeValue(ref hasData); + writer.Write((ushort) Data.Length); + if (Data.IsCreated) { + writer.Write(Data); + } + } - if (hasData) { - serializer.SerializeValue(ref Data, Allocator.Temp); - } + public void Deserialize(NetworkBufferReader reader) { + reader.Read(out ActorID); + reader.Read(out EventID); + reader.Read(out ushort dataLength); + + if (dataLength > 0) { + reader.Read(out Data, dataLength, Allocator.Temp); } } } @@ -342,7 +352,6 @@ namespace RebootKit.Engine.Simulation { // Override this method to implement client-side logic public virtual void OnClientTick(float deltaTime) { } protected virtual void OnActorEventClient(ActorEvent actorEvent) { } - protected virtual void OnClientFinishedInitialSync() { } // // @MARK: Server API @@ -479,7 +488,7 @@ namespace RebootKit.Engine.Simulation { Data = data }; - Manager.SendActorCommandToServerRpc(command); + Manager.SendActorCommand(command); } protected void SendActorEvent(ushort eventID, ref TEventData eventData) @@ -501,7 +510,6 @@ namespace RebootKit.Engine.Simulation { ActorEvent actorEvent = new ActorEvent { ActorID = ActorID, - ClientID = NetworkManager.Singleton.LocalClientId, EventID = eventID, Data = data }; @@ -712,10 +720,6 @@ namespace RebootKit.Engine.Simulation { return CreateActorData(); } - internal void InitialSyncFinished() { - OnClientFinishedInitialSync(); - } - internal void HandleActorCommand(ActorCommand actorCommand) { if (!RR.IsServer()) { s_ActorLogger.Error($"Only the server can handle actor commands. Actor: {name} (ID: {ActorID})"); diff --git a/Runtime/Engine/Code/Simulation/ActorsManager.cs b/Runtime/Engine/Code/Simulation/ActorsManager.cs index c7d4727..38cae06 100644 --- a/Runtime/Engine/Code/Simulation/ActorsManager.cs +++ b/Runtime/Engine/Code/Simulation/ActorsManager.cs @@ -295,16 +295,29 @@ namespace RebootKit.Engine.Simulation { s_Logger.Error($"Failed to find actor with ID {header.ActorID} for event handling."); return; } + + using NetworkBufferReader reader = new NetworkBufferReader(data); + ActorEvent actorEvent = new ActorEvent(); + actorEvent.Deserialize(reader); - throw new NotImplementedException(); + actor.HandleActorEvent(actorEvent); } else if (header.Type == NetworkDataType.ActorCommand) { + if (!RR.IsServer()) { + s_Logger.Error($"Received ActorCommand on client, but this should only be handled on the server."); + return; + } + Actor actor = FindActorByID(header.ActorID); if (actor == null) { s_Logger.Error($"Failed to find actor with ID {header.ActorID} for command handling."); return; } + + using NetworkBufferReader reader = new NetworkBufferReader(data); + ActorCommand actorCommand = new ActorCommand(); + actorCommand.Deserialize(reader); - throw new NotImplementedException(); + actor.HandleActorCommand(actorCommand); } else if (header.Type == NetworkDataType.SynchronizeActor) { Actor actor = FindActorByID(header.ActorID); if (actor == null) { @@ -381,48 +394,17 @@ namespace RebootKit.Engine.Simulation { // // @MARK: Actor Commands and Events // - [Rpc(SendTo.Server, Delivery = RpcDelivery.Reliable)] - internal void SendActorCommandToServerRpc(ActorCommand cmd) { - if (!IsServer) { - s_Logger.Error("Only the server can handle actor events."); - return; - } - - Actor actor = FindActorByID(cmd.ActorID); - if (actor is null) { - s_Logger.Error($"Actor with ID {cmd.ActorID} not found for command {cmd.CommandID}"); - return; - } - - actor.HandleActorCommand(cmd); - } - internal void SendActorEvent(ActorEvent actorEvent) { if (!RR.IsServer()) { s_Logger.Error("Only the server can send actor events."); return; } - - foreach ((ulong clientID, NetworkClientState state) in RR.NetworkSystemInstance.Clients) { - if (NetworkManager.Singleton.LocalClientId == clientID) { - continue; - } - - if (state.IsReady) { - SendActorEventRpc(actorEvent, RpcTarget.Single(clientID, RpcTargetUse.Temp)); - } - } + + RR.NetworkSystemInstance.WriteActorEvent(NetworkPacketTarget.AllClients(), actorEvent); } - [Rpc(SendTo.SpecifiedInParams)] - void SendActorEventRpc(ActorEvent actorEvent, RpcParams rpcParams) { - Actor actor = FindActorByID(actorEvent.ActorID); - if (actor is null) { - s_Logger.Error($"Actor with ID {actorEvent.ActorID} not found for event {actorEvent.EventID}"); - return; - } - - actor.HandleActorEvent(actorEvent); + internal void SendActorCommand(ActorCommand actorCommand) { + RR.NetworkSystemInstance.WriteActorCommand(actorCommand); } } } \ No newline at end of file