From 8e7ddabf5b4745c2294cad9419a35f763c160be0 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 11:45:04 -0500 Subject: [PATCH 1/6] update Adding the SinglePlayerTransport. --- .../Components/Transport.meta | 8 + .../Transport/SinglePlayerTransport.cs | 137 ++++++++++++++++++ .../Transport/SinglePlayerTransport.cs.meta | 11 ++ 3 files changed, 156 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Components/Transport.meta create mode 100644 com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs create mode 100644 com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/Transport.meta b/com.unity.netcode.gameobjects/Components/Transport.meta new file mode 100644 index 0000000000..50df57dbc6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Components/Transport.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1aa8245470655614fab05790b8804943 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs b/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs new file mode 100644 index 0000000000..2f6b688f58 --- /dev/null +++ b/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using Unity.Netcode; + +/// +/// A transport that can be used to run a Netcode for GameObjects sessopm in "single player" mode +/// by assigning this transport to the +/// property before starting a host. +/// +public class SinglePlayerTransport : NetworkTransport +{ + /// + public override ulong ServerClientId { get; } = 0; + + internal static string NotStartingAsHostErrorMessage = $"When using {nameof(SinglePlayerTransport)}, you must start a hosted session so both client and server are available locally."; + + private struct MessageData + { + public ulong FromClientId; + public ArraySegment Payload; + public NetworkEvent Event; + public float AvailableTime; + } + + private static Dictionary> s_MessageQueue = new Dictionary>(); + + private bool m_Initialized; + private ulong m_TransportId = 0; + private NetworkManager m_NetworkManager; + + + /// + public override void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) + { + var copy = new byte[payload.Array.Length]; + Array.Copy(payload.Array, copy, payload.Array.Length); + s_MessageQueue[clientId].Enqueue(new MessageData + { + FromClientId = m_TransportId, + Payload = new ArraySegment(copy, payload.Offset, payload.Count), + Event = NetworkEvent.Data, + AvailableTime = (float)m_NetworkManager.LocalTime.FixedTime, + }); + } + + /// + public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) + { + if (s_MessageQueue[m_TransportId].Count > 0) + { + var data = s_MessageQueue[m_TransportId].Peek(); + if (data.AvailableTime > m_NetworkManager.LocalTime.FixedTime) + { + clientId = 0; + payload = new ArraySegment(); + receiveTime = 0; + return NetworkEvent.Nothing; + } + + s_MessageQueue[m_TransportId].Dequeue(); + clientId = data.FromClientId; + payload = data.Payload; + receiveTime = m_NetworkManager.LocalTime.TimeAsFloat; + if (m_NetworkManager.IsServer && data.Event == NetworkEvent.Connect) + { + s_MessageQueue[data.FromClientId].Enqueue(new MessageData { Event = NetworkEvent.Connect, FromClientId = ServerClientId, Payload = new ArraySegment() }); + } + return data.Event; + } + clientId = 0; + payload = new ArraySegment(); + receiveTime = 0; + return NetworkEvent.Nothing; + } + + /// + /// + /// This will always return false for . + /// Always use . + /// + public override bool StartClient() + { + NetworkLog.LogError(NotStartingAsHostErrorMessage); + return false; + } + + /// + /// + /// Always use when hosting a local single player session. + /// + public override bool StartServer() + { + s_MessageQueue[ServerClientId] = new Queue(); + if (!m_NetworkManager.LocalClient.IsHost && m_NetworkManager.LocalClient.IsServer) + { + NetworkLog.LogError(NotStartingAsHostErrorMessage); + return false; + } + return true; + } + + /// + public override void DisconnectRemoteClient(ulong clientId) + { + s_MessageQueue[clientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = m_TransportId, Payload = new ArraySegment() }); + } + + /// + public override void DisconnectLocalClient() + { + s_MessageQueue[ServerClientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = m_TransportId, Payload = new ArraySegment() }); + } + + /// + /// + /// Will always return 0 since this transport is for a local single player session. + /// + public override ulong GetCurrentRtt(ulong clientId) + { + return 0; + } + + /// + public override void Shutdown() + { + s_MessageQueue.Clear(); + m_TransportId = 0; + } + + /// + public override void Initialize(NetworkManager networkManager = null) + { + s_MessageQueue.Clear(); + m_NetworkManager = networkManager; + } +} + diff --git a/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs.meta b/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs.meta new file mode 100644 index 0000000000..625a116a5b --- /dev/null +++ b/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80bdfd363ee30cb4581ea4405c4888e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 64c072e218119241c54b7dcd82bbac2ad6209bf0 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 11:45:24 -0500 Subject: [PATCH 2/6] test Adding test to validate single player works when started as a host and normal netcode functionality works. Adding tests to validate that NetworkManager will fail to start if started as a client or server when using the SinglePlayerTransport. --- .../Transports/SinglePlayerTransportTests.cs | 210 ++++++++++++++++++ .../SinglePlayerTransportTests.cs.meta | 11 + 2 files changed, 221 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs new file mode 100644 index 0000000000..6e32523b96 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; +using Random = UnityEngine.Random; + +namespace Unity.Netcode.RuntimeTests +{ + internal class SinglePlayerTransportTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + public struct SerializableStruct : INetworkSerializable, IEquatable + { + public bool BoolValue; + public ulong ULongValue; + + public bool Equals(SerializableStruct other) + { + return other.BoolValue == BoolValue && other.ULongValue == ULongValue; + } + + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref BoolValue); + serializer.SerializeValue(ref ULongValue); + } + } + + public class SinglePlayerTestComponent : NetworkBehaviour + { + private enum SpawnStates + { + PreSpawn, + Spawn, + PostSpawn, + } + + private enum RpcInvocations + { + SendToServerRpc, + SendToEveryoneRpc, + SendToOwnerRpc, + } + + private Dictionary m_SpawnStateInvoked = new Dictionary(); + private Dictionary m_RpcInvocations = new Dictionary(); + private NetworkVariable m_IntValue = new NetworkVariable(); + private NetworkVariable m_SerializableValue = new NetworkVariable(); + + + private void SpawnStateInvoked(SpawnStates spawnState) + { + if (!m_SpawnStateInvoked.ContainsKey(spawnState)) + { + m_SpawnStateInvoked.Add(spawnState, 1); + } + else + { + m_SpawnStateInvoked[spawnState]++; + } + } + + private void RpcInvoked(RpcInvocations rpcInvocation) + { + if (!m_RpcInvocations.ContainsKey(rpcInvocation)) + { + m_RpcInvocations.Add(rpcInvocation, 1); + } + else + { + m_RpcInvocations[rpcInvocation]++; + } + } + + private void ValidateValues(int someIntValue, SerializableStruct someValues) + { + Assert.IsTrue(m_IntValue.Value == someIntValue); + Assert.IsTrue(someValues.BoolValue == m_SerializableValue.Value.BoolValue); + Assert.IsTrue(someValues.ULongValue == m_SerializableValue.Value.ULongValue); + } + + [Rpc(SendTo.Server)] + private void SendToServerRpc(int someIntValue, SerializableStruct someValues, RpcParams rpcParams = default) + { + ValidateValues(someIntValue, someValues); + RpcInvoked(RpcInvocations.SendToServerRpc); + } + + [Rpc(SendTo.Everyone)] + private void SendToEveryoneRpc(int someIntValue, SerializableStruct someValues, RpcParams rpcParams = default) + { + ValidateValues(someIntValue, someValues); + RpcInvoked(RpcInvocations.SendToEveryoneRpc); + } + + [Rpc(SendTo.Owner)] + private void SendToOwnerRpc(int someIntValue, SerializableStruct someValues, RpcParams rpcParams = default) + { + ValidateValues(someIntValue, someValues); + RpcInvoked(RpcInvocations.SendToOwnerRpc); + } + + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + SpawnStateInvoked(SpawnStates.PreSpawn); + base.OnNetworkPreSpawn(ref networkManager); + } + + public override void OnNetworkSpawn() + { + SpawnStateInvoked(SpawnStates.Spawn); + m_IntValue.Value = Random.Range(0, 100); + m_SerializableValue.Value = new SerializableStruct() + { + BoolValue = Random.Range(0, 100) >= 50.0 ? true : false, + ULongValue = (ulong)Random.Range(0, 100000), + }; + base.OnNetworkSpawn(); + } + + protected override void OnNetworkPostSpawn() + { + SpawnStateInvoked(SpawnStates.PostSpawn); + SendToServerRpc(m_IntValue.Value, m_SerializableValue.Value); + SendToEveryoneRpc(m_IntValue.Value, m_SerializableValue.Value); + SendToOwnerRpc(m_IntValue.Value, m_SerializableValue.Value); + base.OnNetworkPostSpawn(); + } + + public void ValidateStatesAndRpcInvocations() + { + foreach (var entry in m_SpawnStateInvoked) + { + Assert.True(entry.Value == 1, $"{entry.Key} failed with {entry.Value} invocations!"); + } + foreach (var entry in m_RpcInvocations) + { + Assert.True(entry.Value == 1, $"{entry.Key} failed with {entry.Value} invocations!"); + } + } + } + + private GameObject m_PrefabToSpawn; + private bool m_CanStartHost; + + protected override IEnumerator OnSetup() + { + m_CanStartHost = false; + return base.OnSetup(); + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + + protected override void OnServerAndClientsCreated() + { + var singlePlayerTransport = m_ServerNetworkManager.gameObject.AddComponent(); + m_ServerNetworkManager.NetworkConfig.NetworkTransport = singlePlayerTransport; + m_PrefabToSpawn = CreateNetworkObjectPrefab("TestObject"); + m_PrefabToSpawn.AddComponent(); + base.OnServerAndClientsCreated(); + } + + protected override bool CanStartServerAndClients() + { + return m_CanStartHost; + } + + [UnityTest] + public IEnumerator StartSinglePlayerAndSpawn() + { + m_CanStartHost = true; + + yield return StartServerAndClients(); + + var spawnedInstance = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager).GetComponent(); + var testComponent = spawnedInstance.GetComponent(); + yield return s_DefaultWaitForTick; + var playerTestComponent = m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent(); + testComponent.ValidateStatesAndRpcInvocations(); + playerTestComponent.ValidateStatesAndRpcInvocations(); + } + + [UnityTest] + public IEnumerator StartSinglePlayerAsClientError() + { + LogAssert.Expect(LogType.Error, $"[Netcode] {SinglePlayerTransport.NotStartingAsHostErrorMessage}"); + LogAssert.Expect(LogType.Error, $"[Netcode] Client is shutting down due to network transport start failure of {nameof(SinglePlayerTransport)}!"); + Assert.IsFalse(m_ServerNetworkManager.StartClient()); + yield return null; + } + + [UnityTest] + public IEnumerator StartSinglePlayerAsServerError() + { + LogAssert.Expect(LogType.Error, $"[Netcode] {SinglePlayerTransport.NotStartingAsHostErrorMessage}"); + LogAssert.Expect(LogType.Error, $"[Netcode] Server is shutting down due to network transport start failure of {nameof(SinglePlayerTransport)}!"); + Assert.IsFalse(m_ServerNetworkManager.StartServer()); + yield return null; + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs.meta new file mode 100644 index 0000000000..5c7a643dd7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e6a485adbf124d44da6b526871aff1a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 1eb50d666d9bccc113fc3c4d63a1d66e7502587d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 11:47:52 -0500 Subject: [PATCH 3/6] update Adding change log entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index b96f523884..3d8cc3cc01 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `SinglePlayerTransport` that provides the ability to start as a host for a single player network session. (#3475) - When using UnityTransport >=2.4 and Unity >= 6000.1.0a1, SetConnectionData will accept a fully qualified hostname instead of an IP as a connect address on the client side. (#3440) ### Fixed From 63f909191d83548e228ab0896bb867a48a84a452 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 11:51:03 -0500 Subject: [PATCH 4/6] style Fixing XML API spelling issue and adjusting some of the text copy. --- .../Components/Transport/SinglePlayerTransport.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs b/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs index 2f6b688f58..1265a4c073 100644 --- a/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs +++ b/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs @@ -3,10 +3,13 @@ using Unity.Netcode; /// -/// A transport that can be used to run a Netcode for GameObjects sessopm in "single player" mode -/// by assigning this transport to the -/// property before starting a host. +/// A transport that can be used to run a Netcode for GameObjects session in "single player" mode +/// by assigning this transport to the property before +/// starting as a host. /// +/// +/// You can only start as a host when using this transport. +/// public class SinglePlayerTransport : NetworkTransport { /// From 0379309dc705d408902a1be68aca2c413a6f0091 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 12:30:38 -0500 Subject: [PATCH 5/6] update migrating SinglePlayerTransport into a subfolder of Transports. --- .../Transport/SinglePlayerTransport.cs | 140 ----------------- .../Runtime/Transports/SinglePlayer.meta | 8 + .../SinglePlayer/SinglePlayerTransport.cs | 141 ++++++++++++++++++ .../SinglePlayerTransport.cs.meta | 2 +- .../Transports/SinglePlayerTransportTests.cs | 1 + 5 files changed, 151 insertions(+), 141 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs rename com.unity.netcode.gameobjects/{Components/Transport => Runtime/Transports/SinglePlayer}/SinglePlayerTransport.cs.meta (83%) diff --git a/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs b/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs deleted file mode 100644 index 1265a4c073..0000000000 --- a/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; -using Unity.Netcode; - -/// -/// A transport that can be used to run a Netcode for GameObjects session in "single player" mode -/// by assigning this transport to the property before -/// starting as a host. -/// -/// -/// You can only start as a host when using this transport. -/// -public class SinglePlayerTransport : NetworkTransport -{ - /// - public override ulong ServerClientId { get; } = 0; - - internal static string NotStartingAsHostErrorMessage = $"When using {nameof(SinglePlayerTransport)}, you must start a hosted session so both client and server are available locally."; - - private struct MessageData - { - public ulong FromClientId; - public ArraySegment Payload; - public NetworkEvent Event; - public float AvailableTime; - } - - private static Dictionary> s_MessageQueue = new Dictionary>(); - - private bool m_Initialized; - private ulong m_TransportId = 0; - private NetworkManager m_NetworkManager; - - - /// - public override void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) - { - var copy = new byte[payload.Array.Length]; - Array.Copy(payload.Array, copy, payload.Array.Length); - s_MessageQueue[clientId].Enqueue(new MessageData - { - FromClientId = m_TransportId, - Payload = new ArraySegment(copy, payload.Offset, payload.Count), - Event = NetworkEvent.Data, - AvailableTime = (float)m_NetworkManager.LocalTime.FixedTime, - }); - } - - /// - public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) - { - if (s_MessageQueue[m_TransportId].Count > 0) - { - var data = s_MessageQueue[m_TransportId].Peek(); - if (data.AvailableTime > m_NetworkManager.LocalTime.FixedTime) - { - clientId = 0; - payload = new ArraySegment(); - receiveTime = 0; - return NetworkEvent.Nothing; - } - - s_MessageQueue[m_TransportId].Dequeue(); - clientId = data.FromClientId; - payload = data.Payload; - receiveTime = m_NetworkManager.LocalTime.TimeAsFloat; - if (m_NetworkManager.IsServer && data.Event == NetworkEvent.Connect) - { - s_MessageQueue[data.FromClientId].Enqueue(new MessageData { Event = NetworkEvent.Connect, FromClientId = ServerClientId, Payload = new ArraySegment() }); - } - return data.Event; - } - clientId = 0; - payload = new ArraySegment(); - receiveTime = 0; - return NetworkEvent.Nothing; - } - - /// - /// - /// This will always return false for . - /// Always use . - /// - public override bool StartClient() - { - NetworkLog.LogError(NotStartingAsHostErrorMessage); - return false; - } - - /// - /// - /// Always use when hosting a local single player session. - /// - public override bool StartServer() - { - s_MessageQueue[ServerClientId] = new Queue(); - if (!m_NetworkManager.LocalClient.IsHost && m_NetworkManager.LocalClient.IsServer) - { - NetworkLog.LogError(NotStartingAsHostErrorMessage); - return false; - } - return true; - } - - /// - public override void DisconnectRemoteClient(ulong clientId) - { - s_MessageQueue[clientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = m_TransportId, Payload = new ArraySegment() }); - } - - /// - public override void DisconnectLocalClient() - { - s_MessageQueue[ServerClientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = m_TransportId, Payload = new ArraySegment() }); - } - - /// - /// - /// Will always return 0 since this transport is for a local single player session. - /// - public override ulong GetCurrentRtt(ulong clientId) - { - return 0; - } - - /// - public override void Shutdown() - { - s_MessageQueue.Clear(); - m_TransportId = 0; - } - - /// - public override void Initialize(NetworkManager networkManager = null) - { - s_MessageQueue.Clear(); - m_NetworkManager = networkManager; - } -} - diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer.meta b/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer.meta new file mode 100644 index 0000000000..602b45af91 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 889368c8893d63b4aa3cf53ff3427dd4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs new file mode 100644 index 0000000000..b7b22e764d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; + +namespace Unity.Netcode.Transports.SinglePlayer +{ + /// + /// A transport that can be used to run a Netcode for GameObjects session in "single player" mode + /// by assigning this transport to the property before + /// starting as a host. + /// + /// + /// You can only start as a host when using this transport. + /// + public class SinglePlayerTransport : NetworkTransport + { + /// + public override ulong ServerClientId { get; } = 0; + + internal static string NotStartingAsHostErrorMessage = $"When using {nameof(SinglePlayerTransport)}, you must start a hosted session so both client and server are available locally."; + + private struct MessageData + { + public ulong FromClientId; + public ArraySegment Payload; + public NetworkEvent Event; + public float AvailableTime; + } + + private static Dictionary> s_MessageQueue = new Dictionary>(); + + private bool m_Initialized; + private ulong m_TransportId = 0; + private NetworkManager m_NetworkManager; + + + /// + public override void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) + { + var copy = new byte[payload.Array.Length]; + Array.Copy(payload.Array, copy, payload.Array.Length); + s_MessageQueue[clientId].Enqueue(new MessageData + { + FromClientId = m_TransportId, + Payload = new ArraySegment(copy, payload.Offset, payload.Count), + Event = NetworkEvent.Data, + AvailableTime = (float)m_NetworkManager.LocalTime.FixedTime, + }); + } + + /// + public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) + { + if (s_MessageQueue[m_TransportId].Count > 0) + { + var data = s_MessageQueue[m_TransportId].Peek(); + if (data.AvailableTime > m_NetworkManager.LocalTime.FixedTime) + { + clientId = 0; + payload = new ArraySegment(); + receiveTime = 0; + return NetworkEvent.Nothing; + } + + s_MessageQueue[m_TransportId].Dequeue(); + clientId = data.FromClientId; + payload = data.Payload; + receiveTime = m_NetworkManager.LocalTime.TimeAsFloat; + if (m_NetworkManager.IsServer && data.Event == NetworkEvent.Connect) + { + s_MessageQueue[data.FromClientId].Enqueue(new MessageData { Event = NetworkEvent.Connect, FromClientId = ServerClientId, Payload = new ArraySegment() }); + } + return data.Event; + } + clientId = 0; + payload = new ArraySegment(); + receiveTime = 0; + return NetworkEvent.Nothing; + } + + /// + /// + /// This will always return false for . + /// Always use . + /// + public override bool StartClient() + { + NetworkLog.LogError(NotStartingAsHostErrorMessage); + return false; + } + + /// + /// + /// Always use when hosting a local single player session. + /// + public override bool StartServer() + { + s_MessageQueue[ServerClientId] = new Queue(); + if (!m_NetworkManager.LocalClient.IsHost && m_NetworkManager.LocalClient.IsServer) + { + NetworkLog.LogError(NotStartingAsHostErrorMessage); + return false; + } + return true; + } + + /// + public override void DisconnectRemoteClient(ulong clientId) + { + s_MessageQueue[clientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = m_TransportId, Payload = new ArraySegment() }); + } + + /// + public override void DisconnectLocalClient() + { + s_MessageQueue[ServerClientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = m_TransportId, Payload = new ArraySegment() }); + } + + /// + /// + /// Will always return 0 since this transport is for a local single player session. + /// + public override ulong GetCurrentRtt(ulong clientId) + { + return 0; + } + + /// + public override void Shutdown() + { + s_MessageQueue.Clear(); + m_TransportId = 0; + } + + /// + public override void Initialize(NetworkManager networkManager = null) + { + s_MessageQueue.Clear(); + m_NetworkManager = networkManager; + } + } +} diff --git a/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs.meta b/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs.meta similarity index 83% rename from com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs.meta index 625a116a5b..f25e5bfc84 100644 --- a/com.unity.netcode.gameobjects/Components/Transport/SinglePlayerTransport.cs.meta +++ b/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 80bdfd363ee30cb4581ea4405c4888e3 +guid: f0dbadd1461dfad4094dd12411d30d7f MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs index 6e32523b96..4f198c73d2 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.Transports.SinglePlayer; using UnityEngine; using UnityEngine.TestTools; using Random = UnityEngine.Random; From f85a8d4fff60c7c950681e9d546a5fc777185b26 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 2 Jun 2025 12:55:17 -0500 Subject: [PATCH 6/6] fix-PVP Removing transport meta file. --- com.unity.netcode.gameobjects/Components/Transport.meta | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Components/Transport.meta diff --git a/com.unity.netcode.gameobjects/Components/Transport.meta b/com.unity.netcode.gameobjects/Components/Transport.meta deleted file mode 100644 index 50df57dbc6..0000000000 --- a/com.unity.netcode.gameobjects/Components/Transport.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 1aa8245470655614fab05790b8804943 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: