diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c084534fbd..383865721f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,5 +4,5 @@ Profiling/ @Unity-Technologies/multiplayer-tools /com.unity.netcode.gameobjects/Runtime/Transports/ @Unity-Technologies/server-team /com.unity.netcode.gameobjects/Runtime/SceneManagement/ @NoelStephensUnity -/com.unity.multiplayer.transport.utp/ @Unity-Technologies/server-team +/com.unity.netcode.adapter.utp/ @Unity-Technologies/server-team Documentation~/ @Briancoughlin diff --git a/.yamato/project.metafile b/.yamato/project.metafile index ff1780e842..d6fc23d246 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -26,8 +26,8 @@ projects: packages: - name: com.unity.netcode.gameobjects path: com.unity.netcode.gameobjects - - name: com.unity.multiplayer.transport.utp - path: com.unity.multiplayer.transport.utp + - name: com.unity.netcode.adapter.utp + path: com.unity.netcode.adapter.utp test_editors: - 2021.1 - 2021.2 diff --git a/README.md b/README.md index 43645895bf..e513958259 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ We follow the [Gitflow Workflow](https://www.atlassian.com/git/tutorials/compari This repository is broken into multiple components, each one implemented as a Unity Package. ``` . - ├── com.unity.multiplayer.mlapi # The core netcode SDK unity package (source + tests) - ├── com.unity.multiplayer.transport.utp # Transport wrapper for com.unity.transport experimental package (not currently supported) - └── testproject # A Unity project with various test implementations & scenes which exercise the features in the above package(s). + ├── com.unity.multiplayer.mlapi # The core netcode SDK unity package (source + tests) + ├── com.unity.netcode.adapter.utp # Transport wrapper for com.unity.transport experimental package (not currently supported) + └── testproject # A Unity project with various test implementations & scenes which exercise the features in the above package(s). ``` ### Contributing diff --git a/com.unity.multiplayer.transport.utp/Runtime/UTPTransport.cs b/com.unity.multiplayer.transport.utp/Runtime/UTPTransport.cs deleted file mode 100644 index 0c9526bd61..0000000000 --- a/com.unity.multiplayer.transport.utp/Runtime/UTPTransport.cs +++ /dev/null @@ -1,432 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Unity.Netcode; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Jobs; -using Unity.Networking.Transport; -using UnityEngine; -using UnityEngine.Assertions; -using NetworkEvent = Unity.Networking.Transport.NetworkEvent; -using NetcodeEvent = Unity.Netcode.NetworkEvent; - -[StructLayout(LayoutKind.Explicit)] -public unsafe struct RawNetworkMessage -{ - [FieldOffset(0)] public int Length; - [FieldOffset(4)] public uint Type; - [FieldOffset(8)] public int Id; - [FieldOffset(12)] public fixed byte Data[NetworkParameterConstants.MTU]; -} - -[BurstCompile] -internal struct ClientUpdateJob : IJob -{ - public NetworkDriver Driver; - public NativeArray Connection; - public NativeQueue PacketData; - - public unsafe void Execute() - { - if (!Connection[0].IsCreated) - { - return; - } - - NetworkEvent.Type cmd; - while ((cmd = Connection[0].PopEvent(Driver, out var streamReader)) != NetworkEvent.Type.Empty) - { - if (cmd == NetworkEvent.Type.Connect) - { - var rawMsg = new RawNetworkMessage - { - Length = 0, - Type = (uint)NetcodeEvent.Connect, - Id = Connection[0].InternalId - }; - - PacketData.Enqueue(rawMsg); - } - else if (cmd == NetworkEvent.Type.Data) - { - int messageSize = streamReader.ReadInt(); - - var temp = new NativeArray(messageSize, Allocator.Temp); - streamReader.ReadBytes(temp); - - var rawMsg = new RawNetworkMessage - { - Length = messageSize, - Type = (uint)NetcodeEvent.Data, - Id = Connection[0].InternalId - }; - - UnsafeUtility.MemCpy(rawMsg.Data, temp.GetUnsafePtr(), rawMsg.Length); - - PacketData.Enqueue(rawMsg); - } - else if (cmd == NetworkEvent.Type.Disconnect) - { - Connection[0] = default; - } - } - } -} - -[BurstCompile] -internal struct ServerUpdateJob : IJobParallelForDefer -{ - public NetworkDriver.Concurrent Driver; - public NativeArray Connections; - public NativeQueue.ParallelWriter PacketData; - - private unsafe void QueueMessage(ref DataStreamReader streamReader, int index) - { - int messageSize = streamReader.ReadInt(); - - var temp = new NativeArray(messageSize, Allocator.Temp); - streamReader.ReadBytes(temp); - - var rawMsg = new RawNetworkMessage() - { - Length = messageSize, - Type = (uint)NetcodeEvent.Data, - Id = index - }; - - UnsafeUtility.MemCpy(rawMsg.Data, temp.GetUnsafePtr(), rawMsg.Length); - - PacketData.Enqueue(rawMsg); - } - - public void Execute(int index) - { - Assert.IsTrue(Connections[index].IsCreated); - - NetworkEvent.Type command; - while ((command = Driver.PopEventForConnection(Connections[index], out var streamReader)) != NetworkEvent.Type.Empty) - { - if (command == NetworkEvent.Type.Data) - { - QueueMessage(ref streamReader, index); - } - else if (command == NetworkEvent.Type.Connect) - { - var rawMsg = new RawNetworkMessage - { - Length = 0, - Type = (uint)NetcodeEvent.Connect, - Id = index - }; - - PacketData.Enqueue(rawMsg); - } - else if (command == NetworkEvent.Type.Disconnect) - { - var rawMsg = new RawNetworkMessage - { - Length = 0, - Type = (uint)NetcodeEvent.Disconnect, - Id = index - }; - - PacketData.Enqueue(rawMsg); - Connections[index] = default; - } - } - } -} - -[BurstCompile] -internal struct ServerUpdateConnectionsJob : IJob -{ - public NetworkDriver Driver; - public NativeList Connections; - public NativeQueue.ParallelWriter PacketData; - - public void Execute() - { - // Clean up connections - for (int i = 0; i < Connections.Length; i++) - { - if (!Connections[i].IsCreated) - { - Connections.RemoveAtSwapBack(i); - --i; - } - } - // Accept new connections - NetworkConnection conn; - while ((conn = Driver.Accept()) != default) - { - Connections.Add(conn); - var rawMsg = new RawNetworkMessage - { - Length = 0, - Type = (uint)NetcodeEvent.Connect, - Id = conn.InternalId - }; - - PacketData.Enqueue(rawMsg); - Debug.Log("Accepted a connection"); - } - } -} - -public class UTPTransport : NetworkTransport -{ - public string Address = "127.0.0.1"; - public ushort Port = 7777; - - private NetworkDriver m_Driver; - private NativeList m_Connections; - private NativeQueue m_PacketData; - private NativeArray m_PacketProcessBuffer; - - private JobHandle m_JobHandle; - - private bool m_IsClient = false; - private bool m_IsServer = false; - - - public override ulong ServerClientId => 0; - - public override void DisconnectLocalClient() { _ = m_Driver.Disconnect(m_Connections[0]); } - public override void DisconnectRemoteClient(ulong clientId) - { - GetUTPConnectionDetails(clientId, out uint peerId); - var con = GetConnection(peerId); - if (con != default) - { - m_Driver.Disconnect(con); - } - } - - private NetworkConnection GetConnection(uint id) - { - foreach (var item in m_Connections) - { - if (item.InternalId == id) - { - return item; - } - } - - return default; - } - - private readonly NetworkPipeline[] m_NetworkPipelines = new NetworkPipeline[3]; - - public override void Initialize() - { - m_Driver = NetworkDriver.Create(); - - m_NetworkPipelines[0] = m_Driver.CreatePipeline(typeof(FragmentationPipelineStage)); - m_NetworkPipelines[1] = m_Driver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - m_NetworkPipelines[2] = m_Driver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - - m_PacketData = new NativeQueue(Allocator.Persistent); - m_PacketProcessBuffer = new NativeArray(1000, Allocator.Persistent); - } - - [BurstCompile] - private void SendToClient(NativeArray packet, ulong clientId, int pipelineIndex) - { - foreach (var targetConn in m_Connections) - { - if (targetConn.InternalId != (int)clientId) - { - continue; - } - - var writer = m_Driver.BeginSend(m_NetworkPipelines[pipelineIndex], targetConn); - - if (!writer.IsCreated) - { - continue; - } - - writer.WriteBytes(packet); - - m_Driver.EndSend(writer); - } - } - - public override unsafe void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) - { - var pipelineIndex = 0; - switch (networkDelivery) - { - case NetworkDelivery.Unreliable: - case NetworkDelivery.UnreliableSequenced: - pipelineIndex = 2; - break; - case NetworkDelivery.Reliable: - case NetworkDelivery.ReliableSequenced: - pipelineIndex = 1; - break; - case NetworkDelivery.ReliableFragmentedSequenced: - pipelineIndex = 0; - break; - } - - GetUTPConnectionDetails(clientId, out uint peerId); - - var writer = new DataStreamWriter(payload.Count + 1 + 4, Allocator.Temp); - writer.WriteInt(payload.Count); - - fixed (byte* dataArrayPtr = payload.Array) - { - writer.WriteBytes(dataArrayPtr, payload.Count); - } - - SendToClient(writer.AsNativeArray(), peerId, pipelineIndex); - } - - public override NetcodeEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) - { - clientId = 0; - - payload = new ArraySegment(Array.Empty()); - receiveTime = 0; - - return NetcodeEvent.Nothing; - } - - public override ulong GetCurrentRtt(ulong clientId) => 0; - - private void Update() - { - if (m_IsServer || m_IsClient) - { - while (m_PacketData.TryDequeue(out var message)) - { - var data = m_PacketProcessBuffer.Slice(0, message.Length); - unsafe - { - UnsafeUtility.MemClear(data.GetUnsafePtr(), message.Length); - UnsafeUtility.MemCpy(data.GetUnsafePtr(), message.Data, message.Length); - } - var clientId = GetNetcodeClientId((uint)message.Id, false); - - switch ((NetcodeEvent)message.Type) - { - case NetcodeEvent.Data: - int size = message.Length; - byte[] arr = new byte[size]; - unsafe - { - Marshal.Copy((IntPtr)message.Data, arr, 0, size); - var payload = new ArraySegment(arr); - InvokeOnTransportEvent((NetcodeEvent)message.Type, clientId, payload, Time.realtimeSinceStartup); - } - - break; - case NetcodeEvent.Connect: - { - InvokeOnTransportEvent((NetcodeEvent)message.Type, clientId, new ArraySegment(), Time.realtimeSinceStartup); - } - break; - case NetcodeEvent.Disconnect: - InvokeOnTransportEvent((NetcodeEvent)message.Type, clientId, new ArraySegment(), Time.realtimeSinceStartup); - break; - case NetcodeEvent.Nothing: - InvokeOnTransportEvent((NetcodeEvent)message.Type, clientId, new ArraySegment(), Time.realtimeSinceStartup); - break; - } - } - - - if (m_JobHandle.IsCompleted) - { - if (m_IsServer) - { - var connectionJob = new ServerUpdateConnectionsJob - { - Driver = m_Driver, - Connections = m_Connections, - PacketData = m_PacketData.AsParallelWriter() - }; - - var serverUpdateJob = new ServerUpdateJob - { - Driver = m_Driver.ToConcurrent(), - Connections = m_Connections.AsDeferredJobArray(), - PacketData = m_PacketData.AsParallelWriter() - }; - - m_JobHandle = m_Driver.ScheduleUpdate(); - m_JobHandle = connectionJob.Schedule(m_JobHandle); - m_JobHandle = serverUpdateJob.Schedule(m_Connections, 1, m_JobHandle); - } - - if (m_IsClient) - { - var job = new ClientUpdateJob - { - Driver = m_Driver, - Connection = m_Connections, - PacketData = m_PacketData - }; - m_JobHandle = m_Driver.ScheduleUpdate(); - m_JobHandle = job.Schedule(m_JobHandle); - } - } - - m_JobHandle.Complete(); - } - } - - public override void Shutdown() - { - m_JobHandle.Complete(); - - if (m_PacketData.IsCreated) - { - m_PacketData.Dispose(); - } - - if (m_Connections.IsCreated) - { - m_Connections.Dispose(); - } - - m_Driver.Dispose(); - m_PacketProcessBuffer.Dispose(); - } - - public override SocketTasks StartClient() - { - m_Connections = new NativeList(1, Allocator.Persistent); - var endpoint = NetworkEndPoint.Parse(Address, Port); - m_Connections.Add(m_Driver.Connect(endpoint)); - m_IsClient = true; - - Debug.Log("StartClient"); - return SocketTask.Working.AsTasks(); - } - - private ulong GetNetcodeClientId(uint peerId, bool isServer) => isServer ? (ulong)0 : peerId + 1; - private void GetUTPConnectionDetails(ulong clientId, out uint peerId) => peerId = clientId == 0 ? (uint)ServerClientId : (uint)clientId - 1; - - public override SocketTasks StartServer() - { - m_Connections = new NativeList(0xFF, Allocator.Persistent); - var endpoint = NetworkEndPoint.Parse(Address, Port); - m_IsServer = true; - - Debug.Log("StartServer"); - - if (m_Driver.Bind(endpoint) != 0) - { - Debug.LogError("Failed to bind to port " + Port); - } - else - { - m_Driver.Listen(); - } - - return SocketTask.Working.AsTasks(); - } -} diff --git a/com.unity.multiplayer.transport.utp/Runtime/Utilities.cs b/com.unity.multiplayer.transport.utp/Runtime/Utilities.cs deleted file mode 100644 index c60931e61c..0000000000 --- a/com.unity.multiplayer.transport.utp/Runtime/Utilities.cs +++ /dev/null @@ -1,64 +0,0 @@ - -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; - -namespace Assets.Scripts.Transport -{ - public static class Utilities - { - private static unsafe byte[] SerializeUnmanagedArray(NativeArray value) where T : unmanaged - { - var bytes = new byte[UnsafeUtility.SizeOf() * value.Length + sizeof(int)]; - fixed (byte* ptr = bytes) - { - var buf = new UnsafeAppendBuffer(ptr, bytes.Length); - buf.Add(value); - } - - return bytes; - } - - private static unsafe NativeArray DeserializeUnmanagedArray(byte[] buffer, Allocator allocator = Allocator.Temp) where T : unmanaged - { - fixed (byte* ptr = buffer) - { - var buf = new UnsafeAppendBuffer.Reader(ptr, buffer.Length); - buf.ReadNext(out var array, allocator); - return array; - } - } - - public unsafe static byte[] SerializeUnmanaged(ref T value) where T : unmanaged - { - var bytes = new byte[UnsafeUtility.SizeOf()]; - fixed (byte* ptr = bytes) - { - UnsafeUtility.CopyStructureToPtr(ref value, ptr); - } - - return bytes; - } - - public unsafe static T DeserializeUnmanaged(byte[] buffer) where T : unmanaged - { - fixed (byte* ptr = buffer) - { - UnsafeUtility.CopyPtrToStructure(ptr, out var value); - return value; - } - } - - public unsafe static T DeserializeUnmanaged(ref NativeSlice buffer) where T : unmanaged - { - int structSize = UnsafeUtility.SizeOf(); - long ptr = (long)buffer.GetUnsafePtr(); - long size = buffer.Length; - - long addr = ptr + size - structSize; - - var data = UnsafeUtility.ReadArrayElement((void*)addr, 0); - - return data; - } - } -} diff --git a/com.unity.multiplayer.transport.utp/Runtime/com.unity.multiplayer.transport.utp.asmdef b/com.unity.multiplayer.transport.utp/Runtime/com.unity.multiplayer.transport.utp.asmdef deleted file mode 100644 index d40cfa55ab..0000000000 --- a/com.unity.multiplayer.transport.utp/Runtime/com.unity.multiplayer.transport.utp.asmdef +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "Unity.Multiplayer.Transport.UTP", - "references": [ - "Unity.Collections", - "Unity.Networking.Transport", - "Unity.Jobs", - "Unity.Burst", - "Unity.Netcode.Runtime" - ], - "allowUnsafeCode": true -} \ No newline at end of file diff --git a/com.unity.multiplayer.transport.utp/Tests/Editor/BasicUTPTest.cs b/com.unity.multiplayer.transport.utp/Tests/Editor/BasicUTPTest.cs deleted file mode 100644 index d4c8af3e84..0000000000 --- a/com.unity.multiplayer.transport.utp/Tests/Editor/BasicUTPTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using UnityEngine; -using NUnit.Framework; - -namespace Unity.Netcode.UTP.EditorTests -{ - public class BasicUTPTest : MonoBehaviour - { - [Test] - public void BasicUTPInitializationTest() - { - var o = new GameObject(); - var utpTransport = (UTPTransport)o.AddComponent(typeof(UTPTransport)); - utpTransport.Initialize(); - - Assert.True(utpTransport.ServerClientId == 0); - - utpTransport.Shutdown(); - } - } -} diff --git a/com.unity.multiplayer.transport.utp/Tests/Runtime/com.unity.multiplayer.transport.utp.runtimetests.asmdef b/com.unity.multiplayer.transport.utp/Tests/Runtime/com.unity.multiplayer.transport.utp.runtimetests.asmdef deleted file mode 100644 index f52a330f4e..0000000000 --- a/com.unity.multiplayer.transport.utp/Tests/Runtime/com.unity.multiplayer.transport.utp.runtimetests.asmdef +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "Unity.Multiplayer.Transport.UTP.RuntimeTests", - "rootNamespace": "Unity.Netcode.UTP.RuntimeTests", - "references": [ - "Unity.Netcode.Runtime", - "Unity.Networking.Transport" - ], - "optionalUnityReferences": [ - "TestAssemblies" - ], - "defineConstraints": [ - "UNITY_INCLUDE_TESTS" - ] -} \ No newline at end of file diff --git a/com.unity.multiplayer.transport.utp/CHANGELOG.md b/com.unity.netcode.adapter.utp/CHANGELOG.md similarity index 72% rename from com.unity.multiplayer.transport.utp/CHANGELOG.md rename to com.unity.netcode.adapter.utp/CHANGELOG.md index 02f0a7a072..17d0449a3c 100644 --- a/com.unity.multiplayer.transport.utp/CHANGELOG.md +++ b/com.unity.netcode.adapter.utp/CHANGELOG.md @@ -2,4 +2,4 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) ## [0.0.1-preview.1] - 2020-12-20 -This is the first release of Unity MLAPI Package +This is the first release of Unity Transport for Netcode for Gameobjects diff --git a/com.unity.multiplayer.transport.utp/CHANGELOG.md.meta b/com.unity.netcode.adapter.utp/CHANGELOG.md.meta similarity index 100% rename from com.unity.multiplayer.transport.utp/CHANGELOG.md.meta rename to com.unity.netcode.adapter.utp/CHANGELOG.md.meta diff --git a/com.unity.multiplayer.transport.utp/Documentation~/Manual.md b/com.unity.netcode.adapter.utp/Documentation~/Manual.md similarity index 100% rename from com.unity.multiplayer.transport.utp/Documentation~/Manual.md rename to com.unity.netcode.adapter.utp/Documentation~/Manual.md diff --git a/com.unity.netcode.adapter.utp/Editor.meta b/com.unity.netcode.adapter.utp/Editor.meta new file mode 100644 index 0000000000..75ed8837de --- /dev/null +++ b/com.unity.netcode.adapter.utp/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 191a641d72a2e49e0876f4c985b1bdab +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.adapter.utp/Editor/MultiplayerWindow.cs b/com.unity.netcode.adapter.utp/Editor/MultiplayerWindow.cs new file mode 100644 index 0000000000..0a8c8305b8 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Editor/MultiplayerWindow.cs @@ -0,0 +1,60 @@ +using UnityEditor; +using UnityEngine; + +namespace Unity.Netcode.Editor +{ + public class MultiplayerWindow : EditorWindow + { + private const string k_PrefsKeyPrefix = "NetcodeGameObjects"; + + [MenuItem("Netcode/Simulator Tools")] + public static void ShowWindow() + { + GetWindow(false, "Simulator Tools", true); + } + + private void OnGUI() + { + EditorInt("Client send/recv delay (ms)", "ClientDelay", 0, 2000); + EditorInt("Client send/recv jitter (ms)", "ClientJitter", 0, 200); + EditorInt("Client packet drop (percentage)", "ClientDropRate", 0, 100); + } + + private static string GetKey(string subKey) + { + return k_PrefsKeyPrefix + "_" + Application.productName + "_" + subKey; + } + + private int EditorInt(string label, string key = null, int minValue = int.MinValue, int maxValue = int.MaxValue) + { + string prefsKey = (string.IsNullOrEmpty(key) ? GetKey(label) : GetKey(key)); + int value; + value = EditorPrefs.GetInt(prefsKey); + + if (value < minValue) + { + value = minValue; + } + + if (value > maxValue) + { + value = maxValue; + } + + value = EditorGUILayout.IntField(label, value); + if (value < minValue) + { + value = minValue; + } + + if (value > maxValue) + { + value = maxValue; + } + + EditorPrefs.SetInt(prefsKey, value); + + return value; + } + } +} diff --git a/com.unity.multiplayer.transport.utp/Runtime/Utilities.cs.meta b/com.unity.netcode.adapter.utp/Editor/MultiplayerWindow.cs.meta similarity index 83% rename from com.unity.multiplayer.transport.utp/Runtime/Utilities.cs.meta rename to com.unity.netcode.adapter.utp/Editor/MultiplayerWindow.cs.meta index 871f561f6e..92757498fb 100644 --- a/com.unity.multiplayer.transport.utp/Runtime/Utilities.cs.meta +++ b/com.unity.netcode.adapter.utp/Editor/MultiplayerWindow.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4613e18f31d730a4c908e335ae40f832 +guid: c3b1aa39632a3442d89c1bcac12d94b3 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.netcode.adapter.utp/Editor/com.unity.netcode.adapter.utp.editor.asmdef b/com.unity.netcode.adapter.utp/Editor/com.unity.netcode.adapter.utp.editor.asmdef new file mode 100644 index 0000000000..cd3256c0dc --- /dev/null +++ b/com.unity.netcode.adapter.utp/Editor/com.unity.netcode.adapter.utp.editor.asmdef @@ -0,0 +1,22 @@ +{ + "name": "Unity.Netcode.Adapter.UTP.Editor", + "rootNamespace": "", + "references": [ + "Unity.Collections", + "Unity.Jobs", + "Unity.Burst", + "Unity.Netcode.Runtime", + "Unity.Networking.Transport" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/com.unity.multiplayer.transport.utp/Tests/Editor/com.unity.multiplayer.transport.utp.editortests.asmdef.meta b/com.unity.netcode.adapter.utp/Editor/com.unity.netcode.adapter.utp.editor.asmdef.meta similarity index 76% rename from com.unity.multiplayer.transport.utp/Tests/Editor/com.unity.multiplayer.transport.utp.editortests.asmdef.meta rename to com.unity.netcode.adapter.utp/Editor/com.unity.netcode.adapter.utp.editor.asmdef.meta index c7f78fea6f..a6b631fdec 100644 --- a/com.unity.multiplayer.transport.utp/Tests/Editor/com.unity.multiplayer.transport.utp.editortests.asmdef.meta +++ b/com.unity.netcode.adapter.utp/Editor/com.unity.netcode.adapter.utp.editor.asmdef.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: e7d1cf2f203c677459c30bada5c15a9a +guid: a5fe0308ef4604593b1bd69a244fb186 AssemblyDefinitionImporter: externalObjects: {} userData: diff --git a/com.unity.multiplayer.transport.utp/LICENSE.md b/com.unity.netcode.adapter.utp/LICENSE.md similarity index 100% rename from com.unity.multiplayer.transport.utp/LICENSE.md rename to com.unity.netcode.adapter.utp/LICENSE.md diff --git a/com.unity.multiplayer.transport.utp/LICENSE.md.meta b/com.unity.netcode.adapter.utp/LICENSE.md.meta similarity index 100% rename from com.unity.multiplayer.transport.utp/LICENSE.md.meta rename to com.unity.netcode.adapter.utp/LICENSE.md.meta diff --git a/com.unity.multiplayer.transport.utp/README.md b/com.unity.netcode.adapter.utp/README.md similarity index 100% rename from com.unity.multiplayer.transport.utp/README.md rename to com.unity.netcode.adapter.utp/README.md diff --git a/com.unity.multiplayer.transport.utp/README.md.meta b/com.unity.netcode.adapter.utp/README.md.meta similarity index 100% rename from com.unity.multiplayer.transport.utp/README.md.meta rename to com.unity.netcode.adapter.utp/README.md.meta diff --git a/com.unity.multiplayer.transport.utp/Runtime.meta b/com.unity.netcode.adapter.utp/Runtime.meta similarity index 100% rename from com.unity.multiplayer.transport.utp/Runtime.meta rename to com.unity.netcode.adapter.utp/Runtime.meta diff --git a/com.unity.netcode.adapter.utp/Runtime/UnityTransport.cs b/com.unity.netcode.adapter.utp/Runtime/UnityTransport.cs new file mode 100644 index 0000000000..4c81922120 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Runtime/UnityTransport.cs @@ -0,0 +1,836 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent; +using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Collections; +using Unity.Networking.Transport; +using Unity.Networking.Transport.Relay; +using Unity.Networking.Transport.Utilities; + +namespace Unity.Netcode +{ + /// + /// Provides an interface that overrides the ability to create your own drivers and pipelines + /// + public interface INetworkStreamDriverConstructor + { + void CreateDriver(UnityTransport transport, out NetworkDriver driver, out NetworkPipeline unreliableSequencedPipeline, out NetworkPipeline reliableSequencedPipeline, out NetworkPipeline reliableSequencedFragmentedPipeline); + } + + public class UnityTransport : NetworkTransport, INetworkStreamDriverConstructor + { + public enum ProtocolType + { + UnityTransport, + RelayUnityTransport, + } + + private enum State + { + Disconnected, + Listening, + Connected, + } + + public const int MaximumMessageLength = 6 * 1024; + +#pragma warning disable IDE1006 // Naming Styles + public static INetworkStreamDriverConstructor s_DriverConstructor; +#pragma warning restore IDE1006 // Naming Styles + public INetworkStreamDriverConstructor DriverConstructor => s_DriverConstructor != null ? s_DriverConstructor : this; + + [SerializeField] private ProtocolType m_ProtocolType; + [SerializeField] private int m_MessageBufferSize = MaximumMessageLength; + [SerializeField] private int m_ReciveQueueSize = 128; + [SerializeField] private int m_SendQueueSize = 128; + + [Tooltip("The maximum size of the send queue for batching Netcode events")] + [SerializeField] private int m_SendQueueBatchSize = 4096; + + [SerializeField] private string m_ServerAddress = "127.0.0.1"; + [SerializeField] private ushort m_ServerPort = 7777; + + private State m_State = State.Disconnected; + private NetworkDriver m_Driver; + private List m_NetworkParameters; + private byte[] m_MessageBuffer; + private ulong m_ServerClientId; + + private NetworkPipeline m_UnreliableSequencedPipeline; + private NetworkPipeline m_ReliableSequencedPipeline; + private NetworkPipeline m_ReliableSequencedFragmentedPipeline; + + public override ulong ServerClientId => m_ServerClientId; + + public ProtocolType Protocol => m_ProtocolType; + + private RelayServerData m_RelayServerData; + +#if UNITY_EDITOR + private static int ClientPacketDelayMs => UnityEditor.EditorPrefs.GetInt($"NetcodeGameObjects_{Application.productName}_ClientDelay"); + private static int ClientPacketJitterMs => UnityEditor.EditorPrefs.GetInt($"NetcodeGameObjects_{Application.productName}_ClientJitter"); + private static int ClientPacketDropRate => UnityEditor.EditorPrefs.GetInt($"NetcodeGameObjects_{Application.productName}_ClientDropRate"); +#elif DEVELOPMENT_BUILD + public static int ClientPacketDelayMs = 0; + public static int ClientPacketJitterMs = 0; + public static int ClientPacketDropRate = 0; +#endif +#if UNITY_EDITOR || DEVELOPMENT_BUILD + public SimulatorUtility.Parameters ClientSimulatorParameters + { + get + { + var packetDelay = ClientPacketDelayMs; + var jitter = ClientPacketJitterMs; + if (jitter > packetDelay) + { + jitter = packetDelay; + } + + var packetDrop = ClientPacketDropRate; + int networkRate = 60; // TODO: read from some better place + // All 3 packet types every frame stored for maximum delay, doubled for safety margin + int maxPackets = 2 * (networkRate * 3 * packetDelay + 999) / 1000; + return new SimulatorUtility.Parameters + { + MaxPacketSize = NetworkParameterConstants.MTU, + MaxPacketCount = maxPackets, + PacketDelayMs = packetDelay, + PacketJitterMs = jitter, + PacketDropPercentage = packetDrop + }; + } + } +#endif + + /// + /// SendQueue dictionary is used to batch events instead of sending them immediately. + /// + private readonly Dictionary m_SendQueue = new Dictionary(); + + private void InitDriver() + { + DriverConstructor.CreateDriver(this, out m_Driver, out m_UnreliableSequencedPipeline, out m_ReliableSequencedPipeline, out m_ReliableSequencedFragmentedPipeline); + } + + private void DisposeDriver() + { + if (m_Driver.IsCreated) + { + m_Driver.Dispose(); + } + } + + private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery, int size) + { + switch (delivery) + { + case NetworkDelivery.Unreliable: + return NetworkPipeline.Null; + + case NetworkDelivery.UnreliableSequenced: + return m_UnreliableSequencedPipeline; + + case NetworkDelivery.Reliable: + case NetworkDelivery.ReliableSequenced: + return m_ReliableSequencedPipeline; + + case NetworkDelivery.ReliableFragmentedSequenced: + // No need to send on the fragmented pipeline if data is smaller than MTU. + if (size < NetworkParameterConstants.MTU) + { + return m_ReliableSequencedPipeline; + } + + return m_ReliableSequencedFragmentedPipeline; + + default: + Debug.LogError($"Unknown {nameof(NetworkDelivery)} value: {delivery}"); + return NetworkPipeline.Null; + } + } + + private IEnumerator ClientBindAndConnect(SocketTask task) + { + var serverEndpoint = default(NetworkEndPoint); + + if (m_ProtocolType == ProtocolType.RelayUnityTransport) + { + //This comparison is currently slow since RelayServerData does not implement a custom comparison operator that doesn't use + //reflection, but this does not live in the context of a performance-critical loop, it runs once at initial connection time. + if (m_RelayServerData.Equals(default(RelayServerData))) + { + Debug.LogError("You must call SetRelayServerData() at least once before calling StartRelayServer."); + task.IsDone = true; + task.Success = false; + yield break; + } + + m_NetworkParameters.Add(new RelayNetworkParameter { ServerData = m_RelayServerData }); + } + else + { + serverEndpoint = NetworkEndPoint.Parse(m_ServerAddress, m_ServerPort); + } + + InitDriver(); + + if (m_Driver.Bind(NetworkEndPoint.AnyIpv4) != 0) + { + Debug.LogError("Client failed to bind"); + } + else + { + while (!m_Driver.Bound) + { + yield return null; + } + + var serverConnection = m_Driver.Connect(serverEndpoint); + m_ServerClientId = ParseClientId(serverConnection); + + while (m_Driver.GetConnectionState(serverConnection) == NetworkConnection.State.Connecting) + { + yield return null; + } + + if (m_Driver.GetConnectionState(serverConnection) == NetworkConnection.State.Connected) + { + task.Success = true; + m_State = State.Connected; + } + else + { + Debug.LogError("Client failed to connect to server"); + } + } + + task.IsDone = true; + } + + private IEnumerator ServerBindAndListen(SocketTask task, NetworkEndPoint endPoint) + { + InitDriver(); + + if (m_Driver.Bind(endPoint) != 0) + { + Debug.LogError("Server failed to bind"); + } + else + { + while (!m_Driver.Bound) + { + yield return null; + } + + if (m_Driver.Listen() == 0) + { + task.Success = true; + m_State = State.Listening; + } + else + { + Debug.LogError("Server failed to listen"); + } + } + + task.IsDone = true; + } + + private static RelayAllocationId ConvertFromAllocationIdBytes(byte[] allocationIdBytes) + { + unsafe + { + fixed (byte* ptr = allocationIdBytes) + { + return RelayAllocationId.FromBytePointer(ptr, allocationIdBytes.Length); + } + } + } + + private static RelayHMACKey ConvertFromHMAC(byte[] hmac) + { + unsafe + { + fixed (byte* ptr = hmac) + { + return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length); + } + } + } + + private static RelayConnectionData ConvertConnectionData(byte[] connectionData) + { + unsafe + { + fixed (byte* ptr = connectionData) + { + return RelayConnectionData.FromBytePointer(ptr, RelayConnectionData.k_Length); + } + } + } + + public void SetRelayServerData(string ipv4Address, ushort port, byte[] allocationIdBytes, byte[] keyBytes, + byte[] connectionDataBytes, byte[] hostConnectionDataBytes = null, bool isSecure = false) + { + RelayConnectionData hostConnectionData; + + var serverEndpoint = NetworkEndPoint.Parse(ipv4Address, port); + var allocationId = ConvertFromAllocationIdBytes(allocationIdBytes); + var key = ConvertFromHMAC(keyBytes); + var connectionData = ConvertConnectionData(connectionDataBytes); + + if (hostConnectionDataBytes != null) + { + hostConnectionData = ConvertConnectionData(hostConnectionDataBytes); + } + else + { + hostConnectionData = connectionData; + } + + m_RelayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, + ref hostConnectionData, ref key, isSecure); + m_RelayServerData.ComputeNewNonce(); + } + + private IEnumerator StartRelayServer(SocketTask task) + { + //This comparison is currently slow since RelayServerData does not implement a custom comparison operator that doesn't use + //reflection, but this does not live in the context of a performance-critical loop, it runs once at initial connection time. + if (m_RelayServerData.Equals(default(RelayServerData))) + { + Debug.LogError("You must call SetRelayServerData() at least once before calling StartRelayServer."); + task.IsDone = true; + task.Success = false; + yield break; + } + else + { + m_NetworkParameters.Add(new RelayNetworkParameter { ServerData = m_RelayServerData }); + + yield return ServerBindAndListen(task, NetworkEndPoint.AnyIpv4); + } + } + + private bool AcceptConnection() + { + var connection = m_Driver.Accept(); + + if (connection == default(NetworkConnection)) + { + return false; + } + + InvokeOnTransportEvent(NetcodeNetworkEvent.Connect, + ParseClientId(connection), + default(ArraySegment), + Time.realtimeSinceStartup); + + return true; + + } + + private bool ProcessEvent() + { + var eventType = m_Driver.PopEvent(out var networkConnection, out var reader); + + switch (eventType) + { + case TransportNetworkEvent.Type.Connect: + InvokeOnTransportEvent(NetcodeNetworkEvent.Connect, + ParseClientId(networkConnection), + default(ArraySegment), + Time.realtimeSinceStartup); + return true; + + case TransportNetworkEvent.Type.Disconnect: + InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect, + ParseClientId(networkConnection), + default(ArraySegment), + Time.realtimeSinceStartup); + return true; + + case TransportNetworkEvent.Type.Data: + var isBatched = reader.ReadByte(); + if (isBatched == 1) + { + while (reader.GetBytesRead() < reader.Length) + { + var payloadSize = reader.ReadInt(); + ReadData(payloadSize, ref reader, ref networkConnection); + } + } + else // If is not batched, then read the entire buffer at once + { + var payloadSize = reader.ReadInt(); + + ReadData(payloadSize, ref reader, ref networkConnection); + } + + return true; + } + + return false; + } + + private unsafe void ReadData(int size, ref DataStreamReader reader, ref NetworkConnection networkConnection) + { + if (size > m_MessageBufferSize) + { + Debug.LogError("The received message does not fit into the message buffer"); + } + else + { + unsafe + { + fixed (byte* buffer = &m_MessageBuffer[0]) + { + reader.ReadBytes(buffer, size); + } + } + + InvokeOnTransportEvent(NetcodeNetworkEvent.Data, + ParseClientId(networkConnection), + new ArraySegment(m_MessageBuffer, 0, size), + Time.realtimeSinceStartup + ); + } + } + + private void Update() + { + if (m_Driver.IsCreated) + { + FlushAllSendQueues(); + + m_Driver.ScheduleUpdate().Complete(); + + while (AcceptConnection() && m_Driver.IsCreated) + { + ; + } + + while (ProcessEvent() && m_Driver.IsCreated) + { + ; + } + } + + } + + private void OnDestroy() + { + DisposeDriver(); + } + + private static unsafe ulong ParseClientId(NetworkConnection utpConnectionId) + { + return *(ulong*)&utpConnectionId; + } + + private static unsafe NetworkConnection ParseClientId(ulong netcodeConnectionId) + { + return *(NetworkConnection*)&netcodeConnectionId; + } + + public override void DisconnectLocalClient() + { + Debug.Assert(m_State == State.Connected, "DisconnectLocalClient should be called on a connected client"); + + if (m_State == State.Connected) + { + m_Driver.Disconnect(ParseClientId(m_ServerClientId)); + } + } + + public override void DisconnectRemoteClient(ulong clientId) + { + Debug.Assert(m_State == State.Listening, "DisconnectRemoteClient should be called on a listening server"); + + if (m_State == State.Listening) + { + var connection = ParseClientId(clientId); + + if (m_Driver.GetConnectionState(connection) != NetworkConnection.State.Disconnected) + { + m_Driver.Disconnect(connection); + } + + // we need to cleanup any SendQueues for this connectionID; + var keys = m_SendQueue.Keys.Where(k => k.ClientId == clientId).ToList(); + foreach (var queue in keys) + { + m_SendQueue[queue].Dispose(); + m_SendQueue.Remove(queue); + } + } + } + + public override ulong GetCurrentRtt(ulong clientId) + { + return 0; + } + + public override void Initialize() + { + Debug.Assert(sizeof(ulong) == UnsafeUtility.SizeOf(), + "Netcode connection id size does not match UTP connection id size"); + Debug.Assert(m_MessageBufferSize > 5, "Message buffer size must be greater than 5"); + + m_NetworkParameters = new List(); + + // If we want to be able to actually handle messages MaximumMessageLength bytes in + // size, we need to allow a bit more than that in FragmentationUtility since this needs + // to account for headers and such. 128 bytes is plenty enough for such overhead. + var maxFragmentationCapacity = MaximumMessageLength + 128; + m_NetworkParameters.Add(new FragmentationUtility.Parameters() { PayloadCapacity = maxFragmentationCapacity }); + m_NetworkParameters.Add(new BaselibNetworkParameter() + { + maximumPayloadSize = (uint)m_MessageBufferSize, + receiveQueueCapacity = m_ReciveQueueSize, + sendQueueCapacity = m_SendQueueSize + }); + + m_MessageBuffer = new byte[m_MessageBufferSize]; + } + + public override NetcodeNetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) + { + clientId = default; + payload = default; + receiveTime = default; + return NetcodeNetworkEvent.Nothing; + } + + public override void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) + { + var size = payload.Count + 1 + 4; // 1 extra byte for the channel and another 4 for the count of the data + var pipeline = SelectSendPipeline(networkDelivery, size); + + var sendTarget = new SendTarget(clientId, pipeline); + if (!m_SendQueue.TryGetValue(sendTarget, out var queue)) + { + queue = new SendQueue(m_SendQueueBatchSize); + m_SendQueue.Add(sendTarget, queue); + } + + var success = queue.AddEvent(payload); + if (!success) // This would be false only when the SendQueue is full already or we are sending a super large message at once + { + // If we are in here data exceeded remaining queue size. This should not happen under normal operation. + if (payload.Count > queue.Size) + { + // If data is too large to be batched, flush it out immediately. This happens with large initial spawn packets from Netcode for Gameobjects. + Debug.LogWarning($"Sent {payload.Count} bytes based on delivery method: {networkDelivery}. Event size exceeds sendQueueBatchSize: ({m_SendQueueBatchSize}). This can be the initial payload!"); + Debug.Assert(networkDelivery == NetworkDelivery.ReliableFragmentedSequenced); // Messages like this, should always be sent via the fragmented pipeline. + SendMessageInstantly(sendTarget.ClientId, payload, pipeline); + } + else + { + // Since our queue buffer is full then send that right away, clear it and queue this new data + SendBatchedMessageAndClearQueue(sendTarget, queue); + Debug.Assert(queue.IsEmpty() == true); + queue.Clear(); + queue.AddEvent(payload); + } + } + } + + private unsafe void SendBatchedMessage(ulong clientId, ref NativeArray data, NetworkPipeline pipeline) + { + var payloadSize = data.Length + 1; // One extra byte to mark whether this message is batched or not + var result = m_Driver.BeginSend(pipeline, ParseClientId(clientId), out var writer, payloadSize); + + if (result == 0) + { + if (data.IsCreated) + { + // This 1 byte indicates whether the message has been batched or not, in this case it is + writer.WriteByte(1); + writer.WriteBytes(data); + } + + result = m_Driver.EndSend(writer); + if (result == payloadSize) // If the whole data fit, then we are done here + { + return; + } + } + + Debug.LogError($"Error sending the message {result}"); + } + + private unsafe void SendMessageInstantly(ulong clientId, ArraySegment data, + NetworkPipeline pipeline) + { + var payloadSize = + data.Count + 1 + 4; // 1 byte to indicate if the message is batched and 4 for the payload size + var result = m_Driver.BeginSend(pipeline, ParseClientId(clientId), out var writer, payloadSize); + if (result == 0) + { + if (data.Array != null) + { + writer.WriteByte(0); // This 1 byte indicates whether the message has been batched or not, in this case is not, as is sent instantly + writer.WriteInt(data.Count); + + // Note: we are not writing the one byte for the channel and the other 4 for the data count as it will be handled by the queue + unsafe + { + fixed (byte* dataPtr = &data.Array[data.Offset]) + { + writer.WriteBytes(dataPtr, data.Count); + } + } + } + + result = m_Driver.EndSend(writer); + if (result == payloadSize) // If the whole data fit, then we are done here + { + return; + } + } + + Debug.LogError($"Error sending the message {result}"); + } + + /// + /// Flushes all send queues. + /// + private void FlushAllSendQueues() + { + foreach (var kvp in m_SendQueue) + { + if (kvp.Value.IsEmpty()) + { + continue; + } + + SendBatchedMessageAndClearQueue(kvp.Key, kvp.Value); + } + } + + private void SendBatchedMessageAndClearQueue(SendTarget sendTarget, SendQueue sendQueue) + { + NetworkPipeline pipeline = sendTarget.NetworkPipeline; + var payloadSize = sendQueue.Count + 1; // 1 extra byte to tell whether the message is batched or not + if (payloadSize > NetworkParameterConstants.MTU) // If this is bigger than MTU then force it to be sent via the FragmentedReliableSequencedPipeline + { + pipeline = SelectSendPipeline(NetworkDelivery.ReliableFragmentedSequenced, payloadSize); + } + + var sendBuffer = sendQueue.GetData(); + SendBatchedMessage(sendTarget.ClientId, ref sendBuffer, pipeline); + sendQueue.Clear(); + } + + public override SocketTasks StartClient() + { + if (m_Driver.IsCreated) + { + return SocketTask.Fault.AsTasks(); + } + + var task = SocketTask.Working; + StartCoroutine(ClientBindAndConnect(task)); + return task.AsTasks(); + } + + public override SocketTasks StartServer() + { + if (m_Driver.IsCreated) + { + return SocketTask.Fault.AsTasks(); + } + + var task = SocketTask.Working; + switch (m_ProtocolType) + { + case ProtocolType.UnityTransport: + StartCoroutine(ServerBindAndListen(task, NetworkEndPoint.Parse(m_ServerAddress, m_ServerPort))); + break; + case ProtocolType.RelayUnityTransport: + StartCoroutine(StartRelayServer(task)); + break; + } + + return task.AsTasks(); + } + + public override void Shutdown() + { + DisposeDriver(); + + foreach (var queue in m_SendQueue.Values) + { + queue.Dispose(); + } + + // make sure we don't leak queues when we shutdown + m_SendQueue.Clear(); + } + + public void CreateDriver(UnityTransport transport, out NetworkDriver driver, out NetworkPipeline unreliableSequencedPipeline, out NetworkPipeline reliableSequencedPipeline, out NetworkPipeline reliableSequencedFragmentedPipeline) + { + +#if UNITY_EDITOR || DEVELOPMENT_BUILD + var netParams = new NetworkConfigParameter + { + maxConnectAttempts = NetworkParameterConstants.MaxConnectAttempts, + connectTimeoutMS = NetworkParameterConstants.ConnectTimeoutMS, + disconnectTimeoutMS = NetworkParameterConstants.DisconnectTimeoutMS, + maxFrameTimeMS = 100 + }; + + var simulatorParams = ClientSimulatorParameters; + transport.m_NetworkParameters.Insert(0, simulatorParams); + transport.m_NetworkParameters.Insert(0, netParams); +#endif + if (transport.m_NetworkParameters.Count > 0) + { + driver = NetworkDriver.Create(transport.m_NetworkParameters.ToArray()); + } + else + { + driver = NetworkDriver.Create(); + } +#if UNITY_EDITOR || DEVELOPMENT_BUILD + if (simulatorParams.PacketDelayMs > 0 || simulatorParams.PacketDropInterval > 0) + { + unreliableSequencedPipeline = driver.CreatePipeline( + typeof(UnreliableSequencedPipelineStage), + typeof(SimulatorPipelineStage), + typeof(SimulatorPipelineStageInSend)); + reliableSequencedPipeline = driver.CreatePipeline( + typeof(ReliableSequencedPipelineStage), + typeof(SimulatorPipelineStage), + typeof(SimulatorPipelineStageInSend)); + reliableSequencedFragmentedPipeline = driver.CreatePipeline( + typeof(FragmentationPipelineStage), + typeof(ReliableSequencedPipelineStage), + typeof(SimulatorPipelineStage), + typeof(SimulatorPipelineStageInSend)); + } + else +#endif + { + unreliableSequencedPipeline = driver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); + reliableSequencedPipeline = driver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); + reliableSequencedFragmentedPipeline = driver.CreatePipeline( + typeof(FragmentationPipelineStage), typeof(ReliableSequencedPipelineStage) + ); + } + } + + + // -------------- Utility Types ------------------------------------------------------------------------------- + + /// + /// Memory Stream controller to store several events into one single buffer + /// + private class SendQueue : IDisposable + { + private NativeArray m_Array; + private DataStreamWriter m_Stream; + + /// + /// The size of the send queue. + /// + public int Size { get; } + + public SendQueue(int size) + { + Size = size; + m_Array = new NativeArray(size, Allocator.Persistent); + m_Stream = new DataStreamWriter(m_Array); + } + + /// + /// Ads an event to the send queue. + /// + /// The data to send. + /// True if the event was added successfully to the queue. False if there was no space in the queue. + internal bool AddEvent(ArraySegment data) + { + // Check if we are about to write more than the buffer can fit + // Note: 4 bytes for the count of data + if (m_Stream.Length + data.Count + 4 > Size) + { + return false; + } + + m_Stream.WriteInt(data.Count); + + unsafe + { + fixed (byte* byteData = data.Array) + { + m_Stream.WriteBytes(byteData, data.Count); + } + } + + return true; + } + + internal void Clear() + { + m_Stream.Clear(); + } + + internal bool IsEmpty() + { + return m_Stream.Length == 0; + } + + internal int Count => m_Stream.Length; + + internal NativeArray GetData() + { + return m_Stream.AsNativeArray(); + } + + public void Dispose() + { + m_Array.Dispose(); + } + } + + /// + /// Cached information about reliability mode with a certain client + /// + private struct SendTarget : IEquatable + { + public readonly ulong ClientId; + public readonly NetworkPipeline NetworkPipeline; + + public SendTarget(ulong clientId, NetworkPipeline networkPipeline) + { + ClientId = clientId; + NetworkPipeline = networkPipeline; + } + + public bool Equals(SendTarget other) + { + return ClientId == other.ClientId && NetworkPipeline.Equals(other.NetworkPipeline); + } + + public override bool Equals(object obj) + { + return obj is SendTarget other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return (ClientId.GetHashCode() * 397) ^ NetworkPipeline.GetHashCode(); + } + } + } + } +} diff --git a/com.unity.multiplayer.transport.utp/Runtime/UTPTransport.cs.meta b/com.unity.netcode.adapter.utp/Runtime/UnityTransport.cs.meta similarity index 83% rename from com.unity.multiplayer.transport.utp/Runtime/UTPTransport.cs.meta rename to com.unity.netcode.adapter.utp/Runtime/UnityTransport.cs.meta index e97d1c3054..471eadeb6e 100644 --- a/com.unity.multiplayer.transport.utp/Runtime/UTPTransport.cs.meta +++ b/com.unity.netcode.adapter.utp/Runtime/UnityTransport.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: fc5ef7b69296d69458910681f29471e6 +guid: 6960e84d07fb87f47956e7a81d71c4e6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.netcode.adapter.utp/Runtime/com.unity.netcode.adapter.utp.asmdef b/com.unity.netcode.adapter.utp/Runtime/com.unity.netcode.adapter.utp.asmdef new file mode 100644 index 0000000000..b3e15bf3cc --- /dev/null +++ b/com.unity.netcode.adapter.utp/Runtime/com.unity.netcode.adapter.utp.asmdef @@ -0,0 +1,20 @@ +{ + "name": "Unity.Netcode.Adapter.UTP", + "rootNamespace": "Unity.Netcode.UTP", + "references": [ + "Unity.Collections", + "Unity.Jobs", + "Unity.Burst", + "Unity.Netcode.Runtime", + "Unity.Networking.Transport" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/com.unity.multiplayer.transport.utp/Runtime/com.unity.multiplayer.transport.utp.asmdef.meta b/com.unity.netcode.adapter.utp/Runtime/com.unity.netcode.adapter.utp.asmdef.meta similarity index 76% rename from com.unity.multiplayer.transport.utp/Runtime/com.unity.multiplayer.transport.utp.asmdef.meta rename to com.unity.netcode.adapter.utp/Runtime/com.unity.netcode.adapter.utp.asmdef.meta index 927ee7a884..9de99bab96 100644 --- a/com.unity.multiplayer.transport.utp/Runtime/com.unity.multiplayer.transport.utp.asmdef.meta +++ b/com.unity.netcode.adapter.utp/Runtime/com.unity.netcode.adapter.utp.asmdef.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 3ca8b3b66202abc418c13ff7812fb7ef +guid: 3bf5041814073ec4089849c425919d5a AssemblyDefinitionImporter: externalObjects: {} userData: diff --git a/com.unity.multiplayer.transport.utp/Tests.meta b/com.unity.netcode.adapter.utp/Tests.meta similarity index 100% rename from com.unity.multiplayer.transport.utp/Tests.meta rename to com.unity.netcode.adapter.utp/Tests.meta diff --git a/com.unity.multiplayer.transport.utp/Tests/Editor.meta b/com.unity.netcode.adapter.utp/Tests/Editor.meta similarity index 100% rename from com.unity.multiplayer.transport.utp/Tests/Editor.meta rename to com.unity.netcode.adapter.utp/Tests/Editor.meta diff --git a/com.unity.netcode.adapter.utp/Tests/Editor/UnityTransportTests.cs b/com.unity.netcode.adapter.utp/Tests/Editor/UnityTransportTests.cs new file mode 100644 index 0000000000..8a4b5feee6 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Editor/UnityTransportTests.cs @@ -0,0 +1,92 @@ +using NUnit.Framework; +using UnityEngine; + +namespace Unity.Netcode.UTP.EditorTests +{ + public class UnityTransportTests + { + // Check that starting a server doesn't immediately result in faulted tasks. + [Test] + public void BasicInitServer() + { + UnityTransport transport = new GameObject().AddComponent(); + transport.Initialize(); + + var tasks = transport.StartServer(); + Assert.False(tasks.IsDone && !tasks.Success); + + transport.Shutdown(); + } + + // Check that starting a client doesn't immediately result in faulted tasks. + [Test] + public void BasicInitClient() + { + UnityTransport transport = new GameObject().AddComponent(); + transport.Initialize(); + + var tasks = transport.StartClient(); + Assert.False(tasks.IsDone && !tasks.Success); + + transport.Shutdown(); + } + + // Check that we can't restart a server. + [Test] + public void NoRestartServer() + { + UnityTransport transport = new GameObject().AddComponent(); + transport.Initialize(); + + transport.StartServer(); + var tasks = transport.StartServer(); + Assert.True(tasks.IsDone && !tasks.AnySuccess); + + transport.Shutdown(); + } + + // Check that we can't restart a client. + [Test] + public void NoRestartClient() + { + UnityTransport transport = new GameObject().AddComponent(); + transport.Initialize(); + + transport.StartClient(); + var tasks = transport.StartClient(); + Assert.True(tasks.IsDone && !tasks.AnySuccess); + + transport.Shutdown(); + } + + // Check that we can't start both a server and client on the same transport. + [Test] + public void NotBothServerAndClient() + { + UnityTransport transport; + SocketTasks tasks; + + // Start server then client. + transport = new GameObject().AddComponent(); + transport.Initialize(); + + transport.StartServer(); + tasks = transport.StartClient(); + Assert.True(tasks.IsDone && !tasks.AnySuccess); + + transport.Shutdown(); + + // Start client then server. + transport = new GameObject().AddComponent(); + transport.Initialize(); + + transport.StartClient(); + tasks = transport.StartServer(); + Assert.True(tasks.IsDone && !tasks.AnySuccess); + + transport.Shutdown(); + } + } +} + + diff --git a/com.unity.multiplayer.transport.utp/Tests/Editor/BasicUTPTest.cs.meta b/com.unity.netcode.adapter.utp/Tests/Editor/UnityTransportTests.cs.meta similarity index 83% rename from com.unity.multiplayer.transport.utp/Tests/Editor/BasicUTPTest.cs.meta rename to com.unity.netcode.adapter.utp/Tests/Editor/UnityTransportTests.cs.meta index b984108d7f..0f68020ec5 100644 --- a/com.unity.multiplayer.transport.utp/Tests/Editor/BasicUTPTest.cs.meta +++ b/com.unity.netcode.adapter.utp/Tests/Editor/UnityTransportTests.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4d51a9ee9a7131d47acc9ecb8c99dd65 +guid: 1559305d14837ed449c15c194aff6e6b MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.multiplayer.transport.utp/Tests/Editor/com.unity.multiplayer.transport.utp.editortests.asmdef b/com.unity.netcode.adapter.utp/Tests/Editor/com.unity.netcode.adapter.utp.editortests.asmdef similarity index 72% rename from com.unity.multiplayer.transport.utp/Tests/Editor/com.unity.multiplayer.transport.utp.editortests.asmdef rename to com.unity.netcode.adapter.utp/Tests/Editor/com.unity.netcode.adapter.utp.editortests.asmdef index 5faffd6ef4..71e472c195 100644 --- a/com.unity.multiplayer.transport.utp/Tests/Editor/com.unity.multiplayer.transport.utp.editortests.asmdef +++ b/com.unity.netcode.adapter.utp/Tests/Editor/com.unity.netcode.adapter.utp.editortests.asmdef @@ -1,10 +1,10 @@ { - "name": "Unity.Multiplayer.Transport.UTP.EditorTests", + "name": "Unity.Netcode.Adapter.UTP.EditorTests", "rootNamespace": "Unity.Netcode.UTP.EditorTests", "references": [ "Unity.Netcode.Runtime", "Unity.Networking.Transport", - "Unity.Multiplayer.Transport.UTP" + "Unity.Netcode.Adapter.UTP" ], "optionalUnityReferences": [ "TestAssemblies" diff --git a/com.unity.multiplayer.transport.utp/Tests/Runtime/com.unity.multiplayer.transport.utp.runtimetests.asmdef.meta b/com.unity.netcode.adapter.utp/Tests/Editor/com.unity.netcode.adapter.utp.editortests.asmdef.meta similarity index 76% rename from com.unity.multiplayer.transport.utp/Tests/Runtime/com.unity.multiplayer.transport.utp.runtimetests.asmdef.meta rename to com.unity.netcode.adapter.utp/Tests/Editor/com.unity.netcode.adapter.utp.editortests.asmdef.meta index abefeea927..b80b4eabc4 100644 --- a/com.unity.multiplayer.transport.utp/Tests/Runtime/com.unity.multiplayer.transport.utp.runtimetests.asmdef.meta +++ b/com.unity.netcode.adapter.utp/Tests/Editor/com.unity.netcode.adapter.utp.editortests.asmdef.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 05c2d009acf3b36488f0e38dc7ee0e0f +guid: 0bf62c6e1b90cf343a0d87bd323f46d0 AssemblyDefinitionImporter: externalObjects: {} userData: diff --git a/com.unity.multiplayer.transport.utp/Tests/Runtime.meta b/com.unity.netcode.adapter.utp/Tests/Runtime.meta similarity index 100% rename from com.unity.multiplayer.transport.utp/Tests/Runtime.meta rename to com.unity.netcode.adapter.utp/Tests/Runtime.meta diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/ConnectionTests.cs b/com.unity.netcode.adapter.utp/Tests/Runtime/ConnectionTests.cs new file mode 100644 index 0000000000..d28bfab8d0 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/ConnectionTests.cs @@ -0,0 +1,271 @@ +using NUnit.Framework; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.TestTools; +using static Unity.Netcode.UTP.RuntimeTests.RuntimeTestsHelpers; + +namespace Unity.Netcode.RuntimeTests +{ + public class ConnectionTests + { + // For tests using multiple clients. + private const int k_NumClients = 5; + private UnityTransport m_Server; + private UnityTransport[] m_Clients = new UnityTransport[k_NumClients]; + private List m_ServerEvents; + private List[] m_ClientsEvents = new List[k_NumClients]; + + [UnityTearDown] + public IEnumerator Cleanup() + { + Debug.Log("Calling Cleanup"); + + if (m_Server) + { + m_Server.Shutdown(); + Object.DestroyImmediate(m_Server); + } + + foreach (var transport in m_Clients) + { + if (transport) + { + transport.Shutdown(); + Object.DestroyImmediate(transport); + } + } + + foreach (var transportEvents in m_ClientsEvents) + { + transportEvents?.Clear(); + } + + yield return null; + } + + // Check connection with a single client. + [UnityTest] + public IEnumerator ConnectSingleClient() + { + + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Clients[0], out m_ClientsEvents[0]); + + m_Server.StartServer(); + m_Clients[0].StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + // Check we've received Connect event on client too. + Assert.AreEqual(1, m_ClientsEvents[0].Count); + Assert.AreEqual(NetworkEvent.Connect, m_ClientsEvents[0][0].Type); + + yield return null; + } + + // Check connection with multiple clients. + [UnityTest] + public IEnumerator ConnectMultipleClients() + { + + InitializeTransport(out m_Server, out m_ServerEvents); + m_Server.StartServer(); + + for (int i = 0; i < k_NumClients; i++) + { + InitializeTransport(out m_Clients[i], out m_ClientsEvents[i]); + m_Clients[i].StartClient(); + } + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + // Check that every client also received a Connect event. + Assert.True(m_ClientsEvents.All(evs => evs.Count == 1)); + Assert.True(m_ClientsEvents.All(evs => evs[0].Type == NetworkEvent.Connect)); + + yield return null; + } + + // Check server disconnection with a single client. + [UnityTest] + public IEnumerator ServerDisconnectSingleClient() + { + + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Clients[0], out m_ClientsEvents[0]); + + m_Server.StartServer(); + m_Clients[0].StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + m_Server.DisconnectRemoteClient(m_ServerEvents[0].ClientID); + + yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ClientsEvents[0]); + + yield return null; + } + + // Check server disconnection with multiple clients. + [UnityTest] + public IEnumerator ServerDisconnectMultipleClients() + { + InitializeTransport(out m_Server, out m_ServerEvents); + m_Server.StartServer(); + + for (int i = 0; i < k_NumClients; i++) + { + InitializeTransport(out m_Clients[i], out m_ClientsEvents[i]); + m_Clients[i].StartClient(); + } + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + // Disconnect a single client. + m_Server.DisconnectRemoteClient(m_ServerEvents[0].ClientID); + + // Need to manually wait since we don't know which client will get the Disconnect. + yield return new WaitForSeconds(MaxNetworkEventWaitTime); + + // Check that we received a Disconnect event on only one client. + Assert.AreEqual(1, m_ClientsEvents.Count(evs => evs.Count == 2 && evs[1].Type == NetworkEvent.Disconnect)); + + // Disconnect all the other clients. + for (int i = 1; i < k_NumClients; i++) + { + m_Server.DisconnectRemoteClient(m_ServerEvents[i].ClientID); + } + + // Need to manually wait since we don't know which client got the Disconnect. + yield return new WaitForSeconds(MaxNetworkEventWaitTime); + + // Check that all clients got a Disconnect event. + Assert.True(m_ClientsEvents.All(evs => evs.Count == 2)); + Assert.True(m_ClientsEvents.All(evs => evs[1].Type == NetworkEvent.Disconnect)); + + yield return null; + } + + // Check client disconnection from a single client. + [UnityTest] + public IEnumerator ClientDisconnectSingleClient() + { + + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Clients[0], out m_ClientsEvents[0]); + + m_Server.StartServer(); + m_Clients[0].StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + m_Clients[0].DisconnectLocalClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents); + + yield return null; + } + + // Check client disconnection with multiple clients. + [UnityTest] + public IEnumerator ClientDisconnectMultipleClients() + { + InitializeTransport(out m_Server, out m_ServerEvents); + m_Server.StartServer(); + + for (int i = 0; i < k_NumClients; i++) + { + InitializeTransport(out m_Clients[i], out m_ClientsEvents[i]); + m_Clients[i].StartClient(); + } + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + // Disconnect a single client. + m_Clients[0].DisconnectLocalClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents); + + // Disconnect all the other clients. + for (int i = 1; i < k_NumClients; i++) + { + m_Clients[i].DisconnectLocalClient(); + } + + yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents); + + // Check that we got the correct number of Disconnect events on the server. + Assert.AreEqual(k_NumClients * 2, m_ServerEvents.Count); + Assert.AreEqual(k_NumClients, m_ServerEvents.Count(e => e.Type == NetworkEvent.Disconnect)); + + yield return null; + } + + // Check that server re-disconnects are no-ops. + [UnityTest] + public IEnumerator RepeatedServerDisconnectsNoop() + { + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Clients[0], out m_ClientsEvents[0]); + + m_Server.StartServer(); + m_Clients[0].StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + m_Server.DisconnectRemoteClient(m_ServerEvents[0].ClientID); + + yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ClientsEvents[0]); + + var previousServerEventsCount = m_ServerEvents.Count; + var previousClientEventsCount = m_ClientsEvents[0].Count; + + m_Server.DisconnectRemoteClient(m_ServerEvents[0].ClientID); + + // Need to wait manually since no event should be generated. + yield return new WaitForSeconds(MaxNetworkEventWaitTime); + + // Check we haven't received anything else on the client or server. + Assert.AreEqual(m_ServerEvents.Count, previousServerEventsCount); + Assert.AreEqual(m_ClientsEvents[0].Count, previousClientEventsCount); + + yield return null; + } + + // Check that client re-disconnects are no-ops. + [UnityTest] + public IEnumerator RepeatedClientDisconnectsNoop() + { + + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Clients[0], out m_ClientsEvents[0]); + + m_Server.StartServer(); + m_Clients[0].StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + m_Clients[0].DisconnectLocalClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents); + + var previousServerEventsCount = m_ServerEvents.Count; + var previousClientEventsCount = m_ClientsEvents[0].Count; + + m_Clients[0].DisconnectLocalClient(); + + // Need to wait manually since no event should be generated. + yield return new WaitForSeconds(MaxNetworkEventWaitTime); + + // Check we haven't received anything else on the client or server. + Assert.AreEqual(m_ServerEvents.Count, previousServerEventsCount); + Assert.AreEqual(m_ClientsEvents[0].Count, previousClientEventsCount); + + m_Server.Shutdown(); + + yield return null; + } + } +} diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/ConnectionTests.cs.meta b/com.unity.netcode.adapter.utp/Tests/Runtime/ConnectionTests.cs.meta new file mode 100644 index 0000000000..18635a6b14 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/ConnectionTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e1692ce08a2f72e459680dc1524327a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.multiplayer.transport.utp/Tests/Runtime/DummyTestScript.cs b/com.unity.netcode.adapter.utp/Tests/Runtime/DummyTestScript.cs similarity index 100% rename from com.unity.multiplayer.transport.utp/Tests/Runtime/DummyTestScript.cs rename to com.unity.netcode.adapter.utp/Tests/Runtime/DummyTestScript.cs diff --git a/com.unity.multiplayer.transport.utp/Tests/Runtime/DummyTestScript.cs.meta b/com.unity.netcode.adapter.utp/Tests/Runtime/DummyTestScript.cs.meta similarity index 100% rename from com.unity.multiplayer.transport.utp/Tests/Runtime/DummyTestScript.cs.meta rename to com.unity.netcode.adapter.utp/Tests/Runtime/DummyTestScript.cs.meta diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers.meta b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers.meta new file mode 100644 index 0000000000..27015e3540 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bc69b0a277c4d024eb9f7813ad4db7d1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/DriverClient.cs b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/DriverClient.cs new file mode 100644 index 0000000000..8f577cb61b --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/DriverClient.cs @@ -0,0 +1,91 @@ +using NUnit.Framework; +using System.Collections; +using Unity.Networking.Transport; +using Unity.Networking.Transport.Utilities; +using UnityEngine; + +using UTPNetworkEvent = Unity.Networking.Transport.NetworkEvent; +using static Unity.Netcode.UTP.RuntimeTests.RuntimeTestsHelpers; + +namespace Unity.Netcode.UTP.RuntimeTests +{ + // Thin wrapper around a UTP NetworkDriver that can act as a client to a UnityTransport server. + // In particular that means the pipelines are set up the same way as in UnityTransport. + // + // The only reason it's defined as a MonoBehaviour is that OnDestroy is the only reliable way + // to get the driver's Dispose method called from a UnityTest. Making it disposable would be + // the preferred solution, but that doesn't always mesh well with coroutines. + public class DriverClient : MonoBehaviour + { + private NetworkDriver m_Driver; + public NetworkDriver Driver => m_Driver; + + private NetworkConnection m_Connection; + + private NetworkPipeline m_UnreliableSequencedPipeline; + private NetworkPipeline m_ReliableSequencedPipeline; + private NetworkPipeline m_ReliableSequencedFragmentedPipeline; + + public NetworkPipeline UnreliableSequencedPipeline => m_UnreliableSequencedPipeline; + public NetworkPipeline ReliableSequencedPipeline => m_ReliableSequencedPipeline; + public NetworkPipeline ReliableSequencedFragmentedPipeline => m_ReliableSequencedFragmentedPipeline; + + private NetworkPipeline m_LastEventPipeline; + public NetworkPipeline LastEventPipeline => m_LastEventPipeline; + + private void Awake() + { + var maxCap = UnityTransport.MaximumMessageLength + 128; + var fragParams = new FragmentationUtility.Parameters() { PayloadCapacity = maxCap }; + + m_Driver = NetworkDriver.Create(fragParams); + + m_UnreliableSequencedPipeline = m_Driver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); + m_ReliableSequencedPipeline = m_Driver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); + m_ReliableSequencedFragmentedPipeline = m_Driver.CreatePipeline( + typeof(FragmentationPipelineStage), typeof(ReliableSequencedPipelineStage) + ); + } + + private void Update() + { + m_Driver.ScheduleUpdate().Complete(); + } + + private void OnDestroy() + { + if (m_Driver.IsCreated) + { + m_Driver.Dispose(); + } + } + + public void Connect() + { + var endpoint = NetworkEndPoint.LoopbackIpv4; + endpoint.Port = 7777; + + m_Connection = m_Driver.Connect(endpoint); + } + + // Wait for the given event to be generated by the client's driver. + public IEnumerator WaitForNetworkEvent(UTPNetworkEvent.Type type) + { + float startTime = Time.realtimeSinceStartup; + + while (Time.realtimeSinceStartup - startTime < MaxNetworkEventWaitTime) + { + UTPNetworkEvent.Type eventType = m_Driver.PopEvent(out _, out _, out m_LastEventPipeline); + if (eventType != UTPNetworkEvent.Type.Empty) + { + Assert.AreEqual(type, eventType); + yield break; + } + + yield return null; + } + + Assert.Fail("Timed out while waiting for network event."); + } + } +} diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/DriverClient.cs.meta b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/DriverClient.cs.meta new file mode 100644 index 0000000000..806020f6cf --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/DriverClient.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab8e989afa5cf444bac47c920b5d4748 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/RuntimeTestsHelpers.cs b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/RuntimeTestsHelpers.cs new file mode 100644 index 0000000000..8aeea1ea23 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/RuntimeTestsHelpers.cs @@ -0,0 +1,86 @@ +using NUnit.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Unity.Netcode.UTP.RuntimeTests +{ + public static class RuntimeTestsHelpers + { + // 50ms should be plenty enough for any network interaction to occur (even roundtrips). +#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX + public const float MaxNetworkEventWaitTime = 0.35f; +#else + public const float MaxNetworkEventWaitTime = 0.15f; +#endif + + // Wait for an event to appear in the given event list (must be the very next event). + public static IEnumerator WaitForNetworkEvent(NetworkEvent type, List events) + { + int initialCount = events.Count; + float startTime = Time.realtimeSinceStartup; + + while (Time.realtimeSinceStartup - startTime < MaxNetworkEventWaitTime) + { + if (events.Count > initialCount) + { + Assert.AreEqual(type, events[initialCount].Type); + yield break; + } + + yield return null; + } + + Assert.Fail("Timed out while waiting for network event."); + } + + // Common code to initialize a UnityTransport that logs its events. + public static void InitializeTransport(out UnityTransport transport, out List events) + { + var logger = new TransportEventLogger(); + events = logger.Events; + + transport = new GameObject().AddComponent(); + transport.OnTransportEvent += logger.HandleEvent; + transport.Initialize(); + } + + // Information about an event generated by a transport (basically just the parameters that + // are normally passed along to a TransportEventDelegate). + public struct TransportEvent + { + public NetworkEvent Type; + public ulong ClientID; + public ArraySegment Data; + public float ReceiveTime; + } + + // Utility class that logs events generated by a UnityTransport. Set it up by adding the + // HandleEvent method as an OnTransportEvent delegate of the transport. The list of events + // (in order in which they were generated) can be accessed through the Events property. + public class TransportEventLogger + { + private readonly List m_Events = new List(); + public List Events => m_Events; + + public void HandleEvent(NetworkEvent type, ulong clientID, ArraySegment data, float receiveTime) + { + // Copy the data since the backing array will be reused for future messages. + if (data != default(ArraySegment)) + { + data = new ArraySegment(data.ToArray()); + } + + m_Events.Add(new TransportEvent + { + Type = type, + ClientID = clientID, + Data = data, + ReceiveTime = receiveTime + }); + } + } + } +} diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/RuntimeTestsHelpers.cs.meta b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/RuntimeTestsHelpers.cs.meta new file mode 100644 index 0000000000..9943be2562 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/RuntimeTestsHelpers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c9e62c313ec32744c810875f6738b767 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/TransportTests.cs b/com.unity.netcode.adapter.utp/Tests/Runtime/TransportTests.cs new file mode 100644 index 0000000000..0b1789d1e7 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/TransportTests.cs @@ -0,0 +1,218 @@ +using NUnit.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.TestTools; +using static Unity.Netcode.UTP.RuntimeTests.RuntimeTestsHelpers; + +namespace Unity.Netcode.UTP.RuntimeTests +{ + public class TransportTests + { + private UnityTransport m_Server, m_Client1, m_Client2; + private List m_ServerEvents, m_Client1Events, m_Client2Events; + + [UnityTearDown] + public IEnumerator Cleanup() + { + Debug.Log("Calling Cleanup"); + if (m_Server) + { + m_Server.Shutdown(); + UnityEngine.Object.DestroyImmediate(m_Server); + } + + if (m_Client1) + { + m_Client1.Shutdown(); + UnityEngine.Object.DestroyImmediate(m_Client1); + } + + if (m_Client2) + { + m_Client2.Shutdown(); + UnityEngine.Object.DestroyImmediate(m_Client2); + } + + m_ServerEvents?.Clear(); + m_Client1Events?.Clear(); + m_Client2Events?.Clear(); + + yield return null; + } + + // Check if can make a simple data exchange. + [UnityTest] + public IEnumerator PingPong() + { + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Client1, out m_Client1Events); + + m_Server.StartServer(); + m_Client1.StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + var ping = new ArraySegment(Encoding.ASCII.GetBytes("ping")); + m_Client1.Send(m_Client1.ServerClientId, ping, NetworkDelivery.ReliableSequenced); + + yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents); + + Assert.That(m_ServerEvents[1].Data, Is.EquivalentTo(Encoding.ASCII.GetBytes("ping"))); + + var pong = new ArraySegment(Encoding.ASCII.GetBytes("pong")); + m_Server.Send(m_ServerEvents[0].ClientID, pong, NetworkDelivery.ReliableSequenced); + + yield return WaitForNetworkEvent(NetworkEvent.Data, m_Client1Events); + + Assert.That(m_Client1Events[1].Data, Is.EquivalentTo(Encoding.ASCII.GetBytes("pong"))); + + // server.Shutdown(); + // client.Shutdown(); + + yield return null; + } + + + + // Check if can make a simple data exchange (both ways at a time). + [UnityTest] + public IEnumerator PingPongSimultaneous() + { + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Client1, out m_Client1Events); + + m_Server.StartServer(); + m_Client1.StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + var ping = new ArraySegment(Encoding.ASCII.GetBytes("ping")); + m_Server.Send(m_ServerEvents[0].ClientID, ping, NetworkDelivery.ReliableSequenced); + m_Client1.Send(m_Client1.ServerClientId, ping, NetworkDelivery.ReliableSequenced); + + // Once one event is in the other should be too. + yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents); + + Assert.That(m_ServerEvents[1].Data, Is.EquivalentTo(Encoding.ASCII.GetBytes("ping"))); + Assert.That(m_Client1Events[1].Data, Is.EquivalentTo(Encoding.ASCII.GetBytes("ping"))); + + var pong = new ArraySegment(Encoding.ASCII.GetBytes("pong")); + m_Server.Send(m_ServerEvents[0].ClientID, pong, NetworkDelivery.ReliableSequenced); + m_Client1.Send(m_Client1.ServerClientId, pong, NetworkDelivery.ReliableSequenced); + + // Once one event is in the other should be too. + yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents); + + Assert.That(m_ServerEvents[2].Data, Is.EquivalentTo(Encoding.ASCII.GetBytes("pong"))); + Assert.That(m_Client1Events[2].Data, Is.EquivalentTo(Encoding.ASCII.GetBytes("pong"))); + + yield return null; + } + + // Check making multiple sends to a client in a single frame. + [UnityTest] + public IEnumerator MultipleSendsSingleFrame() + { + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Client1, out m_Client1Events); + + m_Server.StartServer(); + m_Client1.StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + var data1 = new ArraySegment(new byte[] { 11 }); + m_Client1.Send(m_Client1.ServerClientId, data1, NetworkDelivery.ReliableSequenced); + + var data2 = new ArraySegment(new byte[] { 22 }); + m_Client1.Send(m_Client1.ServerClientId, data2, NetworkDelivery.ReliableSequenced); + + yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents); + + Assert.AreEqual(3, m_ServerEvents.Count); + Assert.AreEqual(NetworkEvent.Data, m_ServerEvents[2].Type); + + Assert.AreEqual(11, m_ServerEvents[1].Data.First()); + Assert.AreEqual(22, m_ServerEvents[2].Data.First()); + + yield return null; + } + + // Check sending data to multiple clients. + [UnityTest] + public IEnumerator SendMultipleClients() + { + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Client1, out m_Client1Events); + InitializeTransport(out m_Client2, out m_Client2Events); + + m_Server.StartServer(); + m_Client1.StartClient(); + m_Client2.StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ServerEvents); + + // Ensure we got both Connect events. + Assert.AreEqual(2, m_ServerEvents.Count); + + var data1 = new ArraySegment(new byte[] { 11 }); + m_Server.Send(m_ServerEvents[0].ClientID, data1, NetworkDelivery.ReliableSequenced); + + var data2 = new ArraySegment(new byte[] { 22 }); + m_Server.Send(m_ServerEvents[1].ClientID, data2, NetworkDelivery.ReliableSequenced); + + // Once one has received its data, the other should have too. + yield return WaitForNetworkEvent(NetworkEvent.Data, m_Client1Events); + + // Do make sure the other client got its Data event. + Assert.AreEqual(2, m_Client2Events.Count); + Assert.AreEqual(NetworkEvent.Data, m_Client2Events[1].Type); + + byte c1Data = m_Client1Events[1].Data.First(); + byte c2Data = m_Client2Events[1].Data.First(); + Assert.True((c1Data == 11 && c2Data == 22) || (c1Data == 22 && c2Data == 11)); + + yield return null; + } + + // Check receiving data from multiple clients. + [UnityTest] + public IEnumerator ReceiveMultipleClients() + { + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Client1, out m_Client1Events); + InitializeTransport(out m_Client2, out m_Client2Events); + + m_Server.StartServer(); + m_Client1.StartClient(); + m_Client2.StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events); + + // Ensure we got the Connect event on the other client too. + Assert.AreEqual(1, m_Client2Events.Count); + + var data1 = new ArraySegment(new byte[] { 11 }); + m_Client1.Send(m_Client1.ServerClientId, data1, NetworkDelivery.ReliableSequenced); + + var data2 = new ArraySegment(new byte[] { 22 }); + m_Client2.Send(m_Client2.ServerClientId, data2, NetworkDelivery.ReliableSequenced); + + yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents); + + // Make sure we got both data messages. + Assert.AreEqual(4, m_ServerEvents.Count); + Assert.AreEqual(NetworkEvent.Data, m_ServerEvents[3].Type); + + byte sData1 = m_ServerEvents[2].Data.First(); + byte sData2 = m_ServerEvents[3].Data.First(); + Assert.True((sData1 == 11 && sData2 == 22) || (sData1 == 22 && sData2 == 11)); + + yield return null; + } + } +} diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/TransportTests.cs.meta b/com.unity.netcode.adapter.utp/Tests/Runtime/TransportTests.cs.meta new file mode 100644 index 0000000000..6d81e3bad8 --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/TransportTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c4fedf1be1a7d84ba34595aae75f8da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/com.unity.netcode.adapter.utp.runtimetests.asmdef b/com.unity.netcode.adapter.utp/Tests/Runtime/com.unity.netcode.adapter.utp.runtimetests.asmdef new file mode 100644 index 0000000000..9abd7f4b5d --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/com.unity.netcode.adapter.utp.runtimetests.asmdef @@ -0,0 +1,25 @@ +{ + "name": "Unity.Netcode.Adapter.UTP.RuntimeTests", + "rootNamespace": "Unity.Netcode.UTP.RuntimeTests", + "references": [ + "Unity.Netcode.Runtime", + "Unity.Networking.Transport", + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Netcode.Adapter.UTP" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS", + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/com.unity.netcode.adapter.utp/Tests/Runtime/com.unity.netcode.adapter.utp.runtimetests.asmdef.meta b/com.unity.netcode.adapter.utp/Tests/Runtime/com.unity.netcode.adapter.utp.runtimetests.asmdef.meta new file mode 100644 index 0000000000..af42d3571a --- /dev/null +++ b/com.unity.netcode.adapter.utp/Tests/Runtime/com.unity.netcode.adapter.utp.runtimetests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f61c9ac8cd8eb034b9ab68c718c02763 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.multiplayer.transport.utp/package.json b/com.unity.netcode.adapter.utp/package.json similarity index 72% rename from com.unity.multiplayer.transport.utp/package.json rename to com.unity.netcode.adapter.utp/package.json index 07c09b1398..050da4e23c 100644 --- a/com.unity.multiplayer.transport.utp/package.json +++ b/com.unity.netcode.adapter.utp/package.json @@ -1,12 +1,11 @@ { - "name": "com.unity.multiplayer.transport.utp", + "name": "com.unity.netcode.adapter.utp", "displayName": "Unity Transport for Netcode for GameObjects", "description": "This package is plugging Unity Transport into Netcode for GameObjects, which is a network transport layer - the low-level interface for sending UDP data", "version": "0.0.1-preview.1", "unity": "2020.3", "dependencies": { "com.unity.netcode.gameobjects": "0.0.1-preview.1", - "com.unity.transport": "0.4.1-preview.1", - "com.unity.jobs":"0.2.10-preview.13" + "com.unity.transport": "1.0.0-pre.4" } -} +} \ No newline at end of file diff --git a/com.unity.multiplayer.transport.utp/package.json.meta b/com.unity.netcode.adapter.utp/package.json.meta similarity index 100% rename from com.unity.multiplayer.transport.utp/package.json.meta rename to com.unity.netcode.adapter.utp/package.json.meta diff --git a/com.unity.netcode.gameobjects/README.md b/com.unity.netcode.gameobjects/README.md index 38378716c4..ffa8974a61 100644 --- a/com.unity.netcode.gameobjects/README.md +++ b/com.unity.netcode.gameobjects/README.md @@ -31,9 +31,9 @@ We follow the [Gitflow Workflow](https://www.atlassian.com/git/tutorials/compari This repository is broken into multiple components, each one implemented as a Unity Package. ``` . - ├── com.unity.multiplayer.mlapi # The core netcode SDK unity package (source + tests) - ├── com.unity.multiplayer.transport.utp # Transport wrapper for com.unity.transport experimental package (not currently supported) - └── testproject # A Unity project with various test implementations & scenes which exercise the features in the above package(s). + ├── com.unity.multiplayer.mlapi # The core netcode SDK unity package (source + tests) + ├── com.unity.netcode.adapter.utp # Transport wrapper for com.unity.transport experimental package (not currently supported) + └── testproject # A Unity project with various test implementations & scenes which exercise the features in the above package(s). ``` ### Contributing diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 6bbd2841ba..70833997f6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -905,7 +905,7 @@ public void Shutdown() } } - if (IsClient) + if (IsClient && IsConnectedClient) { // Client only, send disconnect to server NetworkConfig.NetworkTransport.DisconnectLocalClient(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 22ef29e77c..e43c8ac2ee 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests { public struct FixedString32Struct : INetworkSerializable { - public FixedString32 FixedString; + public FixedString32Bytes FixedString; public void NetworkSerialize(NetworkSerializer serializer) { if (serializer.IsReading) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef index 26543c4e4e..eb8b0ef8ba 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef @@ -8,7 +8,8 @@ "Unity.Collections", "UnityEngine.TestRunner", "Unity.Multiplayer.MetricTypes", - "Unity.Multiplayer.NetStats" + "Unity.Multiplayer.NetStats", + "Unity.Netcode.Adapter.UTP" ], "includePlatforms": [], "excludePlatforms": [], @@ -29,4 +30,4 @@ } ], "noEngineReferences": false -} +} \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 43b530af03..4bf79f247d 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -8,6 +8,6 @@ "com.unity.modules.ai": "1.0.0", "com.unity.modules.animation": "1.0.0", "com.unity.nuget.mono-cecil": "1.10.1-preview.1", - "com.unity.collections": "0.12.0-preview.13" + "com.unity.collections": "1.0.0-pre.5" } } diff --git a/testproject-tools-integration/Packages/manifest.json b/testproject-tools-integration/Packages/manifest.json index 7d187e5d57..47c85028ca 100644 --- a/testproject-tools-integration/Packages/manifest.json +++ b/testproject-tools-integration/Packages/manifest.json @@ -4,7 +4,7 @@ "com.unity.ide.rider": "3.0.7", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.multiplayer.tools": "0.0.1-preview.8", - "com.unity.multiplayer.transport.utp": "file:../../com.unity.multiplayer.transport.utp", + "com.unity.netcode.adapter.utp": "file:../../com.unity.netcode.adapter.utp", "com.unity.test-framework": "1.1.26", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", diff --git a/testproject/Assets/Prefabs/ConnectionModeButtons.prefab b/testproject/Assets/Prefabs/ConnectionModeButtons.prefab index cf0e0a55b2..e73e194034 100644 --- a/testproject/Assets/Prefabs/ConnectionModeButtons.prefab +++ b/testproject/Assets/Prefabs/ConnectionModeButtons.prefab @@ -1,5 +1,36 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1 &1249278213434350085 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 598016439257704092} + m_Layer: 0 + m_Name: AuthenticationRoot + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &598016439257704092 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1249278213434350085} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 3244368006698824953} + m_Father: {fileID: 6633621479308595792} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &2956145122089128470 GameObject: m_ObjectHideFlags: 0 @@ -636,6 +667,139 @@ MonoBehaviour: m_VerticalOverflow: 0 m_LineSpacing: 1 m_Text: Create Server +--- !u!1 &6099671099853797807 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3244368006698824953} + - component: {fileID: 6393215408609301555} + - component: {fileID: 419726133693533646} + - component: {fileID: 5726292772173537982} + m_Layer: 5 + m_Name: Authenticate + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3244368006698824953 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6099671099853797807} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 6242334806395911574} + m_Father: {fileID: 598016439257704092} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6393215408609301555 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6099671099853797807} + m_CullTransparentMesh: 1 +--- !u!114 &419726133693533646 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6099671099853797807} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.2, g: 0.2, b: 0.2, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &5726292772173537982 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6099671099853797807} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 419726133693533646} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 4850072633501053442} + m_TargetAssemblyTypeName: ConnectionModeScript, TestProject + m_MethodName: OnSignIn + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 --- !u!1 &6957137875088695540 GameObject: m_ObjectHideFlags: 0 @@ -698,6 +862,7 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: - {fileID: 1265025556613751300} + - {fileID: 598016439257704092} m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -719,3 +884,83 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_ConnectionModeButtons: {fileID: 6957137875088695540} + m_AuthenticationButtons: {fileID: 1249278213434350085} +--- !u!1 &8777614647619606221 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6242334806395911574} + - component: {fileID: 1960081656510161396} + - component: {fileID: 7791526084471880686} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6242334806395911574 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8777614647619606221} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 3244368006698824953} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1960081656510161396 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8777614647619606221} + m_CullTransparentMesh: 1 +--- !u!114 &7791526084471880686 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8777614647619606221} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 0.5058824, b: 0.003921569, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Authenticate diff --git a/testproject/Assets/Scenes/SampleScene.unity b/testproject/Assets/Scenes/SampleScene.unity index 7a89a0777f..1d97bee118 100644 --- a/testproject/Assets/Scenes/SampleScene.unity +++ b/testproject/Assets/Scenes/SampleScene.unity @@ -38,7 +38,7 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 705507994} - m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641275, b: 0.5748172, a: 1} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: @@ -219,6 +219,18 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 19899154} m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} +--- !u!114 &102484700 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 4850072633501053442, guid: d725b5588e1b956458798319e6541d84, + type: 3} + m_PrefabInstance: {fileID: 1928839749} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 50623966c8d88ab40982cc2b0e4c2d2e, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &225870858 GameObject: m_ObjectHideFlags: 0 @@ -263,6 +275,85 @@ BoxCollider: serializedVersion: 2 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &284590168 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 284590169} + - component: {fileID: 284590171} + - component: {fileID: 284590170} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &284590169 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 284590168} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 509741757} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -0.5} + m_SizeDelta: {x: -20, y: -13} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &284590170 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 284590168} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 0 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 1 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: +--- !u!222 &284590171 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 284590168} + m_CullTransparentMesh: 1 --- !u!1 &354062834 GameObject: m_ObjectHideFlags: 0 @@ -677,6 +768,175 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 457860556} m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &509741756 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 509741757} + - component: {fileID: 509741761} + - component: {fileID: 509741760} + - component: {fileID: 509741759} + - component: {fileID: 509741758} + m_Layer: 5 + m_Name: InputField + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &509741757 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 509741756} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 900776506} + - {fileID: 284590169} + m_Father: {fileID: 1333567166} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 10, y: -10} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0, y: 1} +--- !u!114 &509741758 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 509741756} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 903872102732f5c4cbdd3863de5dbc97, type: 3} + m_Name: + m_EditorClassIdentifier: + ConnectionScript: {fileID: 102484700} +--- !u!114 &509741759 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 509741756} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d199490a83bb2b844b9695cbf13b01ef, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 509741760} + m_TextComponent: {fileID: 284590170} + m_Placeholder: {fileID: 900776507} + m_ContentType: 0 + m_InputType: 0 + m_AsteriskChar: 42 + m_KeyboardType: 0 + m_LineType: 0 + m_HideMobileInput: 0 + m_CharacterValidation: 0 + m_CharacterLimit: 0 + m_OnEndEdit: + m_PersistentCalls: + m_Calls: [] + m_OnValueChanged: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 509741758} + m_TargetAssemblyTypeName: RelayJoinCodeInput, TestProject + m_MethodName: SetJoinCode + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_CustomCaretColor: 0 + m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412} + m_Text: + m_CaretBlinkRate: 0.85 + m_CaretWidth: 1 + m_ReadOnly: 0 + m_ShouldActivateOnSelect: 1 +--- !u!114 &509741760 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 509741756} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &509741761 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 509741756} + m_CullTransparentMesh: 1 --- !u!1 &535968794 GameObject: m_ObjectHideFlags: 0 @@ -828,6 +1088,7 @@ GameObject: - component: {fileID: 620561612} - component: {fileID: 620561611} - component: {fileID: 620561610} + - component: {fileID: 620561613} m_Layer: 0 m_Name: '[NetworkManager]' m_TagString: Untagged @@ -842,7 +1103,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 620561609} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: b84c2d8dfe509a34fb59e2b81f8e1319, type: 3} m_Name: @@ -877,14 +1138,23 @@ MonoBehaviour: LogLevel: 1 NetworkConfig: ProtocolVersion: 0 - NetworkTransport: {fileID: 620561610} + NetworkTransport: {fileID: 620561613} RegisteredScenes: - SampleScene AllowRuntimeSceneChanges: 0 PlayerPrefab: {fileID: 8685790303553767886, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} - NetworkPrefabs: [] - TickRate: 30 + NetworkPrefabs: + - Override: 0 + Prefab: {fileID: 8133991607019124060, guid: 421bcf732fe69486d8abecfa5eee63bb, + type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + ReceiveTickrate: 64 + NetworkTickIntervalSec: 0.016 + MaxReceiveEventsPerTickRate: 500 + EventTickrate: 64 ClientConnectionBufferTimeout: 10 ConnectionApproval: 0 ConnectionData: @@ -914,6 +1184,22 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &620561613 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 620561609} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ProtocolType: 1 + m_MessageBufferSize: 1024 + m_ServerAddress: 127.0.0.1 + m_ServerPort: 7777 --- !u!1001 &627808638 PrefabInstance: m_ObjectHideFlags: 0 @@ -1031,6 +1317,11 @@ PrefabInstance: propertyPath: m_Name value: ExitButton objectReference: {fileID: 0} + - target: {fileID: 2848221156307247795, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_IsActive + value: 1 + objectReference: {fileID: 0} - target: {fileID: 5266522511616468950, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} propertyPath: m_SceneMenuToLoad @@ -1726,6 +2017,85 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 878759701} m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &900776505 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 900776506} + - component: {fileID: 900776508} + - component: {fileID: 900776507} + m_Layer: 5 + m_Name: Placeholder + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &900776506 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 900776505} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 509741757} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -0.5} + m_SizeDelta: {x: -20, y: -13} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &900776507 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 900776505} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.5} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 2 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 0 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Relay Join Code +--- !u!222 &900776508 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 900776505} + m_CullTransparentMesh: 1 --- !u!1 &963826002 GameObject: m_ObjectHideFlags: 0 @@ -1868,6 +2238,51 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 963826002} m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &1202924672 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1202924674} + - component: {fileID: 1202924673} + m_Layer: 0 + m_Name: UnityChanSpawner + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1202924673 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1202924672} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 05037a80244af4174806bc7f242e4432, type: 3} + m_Name: + m_EditorClassIdentifier: + UnityChanPrefab: {fileID: 8133991607019124060, guid: 421bcf732fe69486d8abecfa5eee63bb, + type: 3} +--- !u!4 &1202924674 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1202924672} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -10, y: -0.1, z: 10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1237561005 GameObject: m_ObjectHideFlags: 0 @@ -2059,7 +2474,6 @@ GameObject: - component: {fileID: 1333567165} - component: {fileID: 1333567164} - component: {fileID: 1333567163} - - component: {fileID: 1333567167} m_Layer: 5 m_Name: Canvas m_TagString: Untagged @@ -2141,6 +2555,7 @@ RectTransform: m_Children: - {fileID: 627808639} - {fileID: 1536251758} + - {fileID: 509741757} m_Father: {fileID: 0} m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -2149,20 +2564,6 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} ---- !u!114 &1333567167 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1333567162} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 4390d966eb8724ba2907f775b34e94ea, type: 3} - m_Name: - m_EditorClassIdentifier: - NetworkManager: {fileID: 620561611} - ButtonsRoot: {fileID: 0} --- !u!1 &1345111612 GameObject: m_ObjectHideFlags: 0 @@ -3167,6 +3568,46 @@ PrefabInstance: m_Modification: m_TransformParent: {fileID: 1333567166} m_Modifications: + - target: {fileID: 2956145122089128464, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnStartServerButton + objectReference: {fileID: 0} + - target: {fileID: 2956145122089128464, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: ConnectionModeScript, TestProject + objectReference: {fileID: 0} + - target: {fileID: 2956145122546206394, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnStartClientButton + objectReference: {fileID: 0} + - target: {fileID: 2956145122546206394, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: ConnectionModeScript, TestProject + objectReference: {fileID: 0} + - target: {fileID: 2956145122624039697, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnStartHostButton + objectReference: {fileID: 0} + - target: {fileID: 2956145122624039697, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: ConnectionModeScript, TestProject + objectReference: {fileID: 0} + - target: {fileID: 4850072633501053442, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_JoinCodeInput + value: + objectReference: {fileID: 509741756} + - target: {fileID: 4850072633501053442, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_RelayAllocationBasePath + value: https://relay-allocations-stg.services.api.unity.com + objectReference: {fileID: 0} - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, type: 3} propertyPath: m_Pivot.x diff --git a/testproject/Assets/Scripts.meta b/testproject/Assets/Scripts.meta index fbdac1c07d..791808f146 100644 --- a/testproject/Assets/Scripts.meta +++ b/testproject/Assets/Scripts.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7d4cd8efbd22842fb88268393e9d06a2 +guid: d3f222fc986b1e24580a88c62063c3bb folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/testproject/Assets/Scripts/CommandLineHandler.cs b/testproject/Assets/Scripts/CommandLineHandler.cs index bf833aa88d..8955982a84 100644 --- a/testproject/Assets/Scripts/CommandLineHandler.cs +++ b/testproject/Assets/Scripts/CommandLineHandler.cs @@ -163,7 +163,7 @@ private void StartServer() m_CommandLineArguments.Remove("-m"); if (m_ConnectionModeScript) { - m_ConnectionModeScript.OnStartServer(); + m_ConnectionModeScript.OnStartServerButton(); } else { @@ -175,7 +175,7 @@ private void StartHost() { if (m_ConnectionModeScript) { - m_ConnectionModeScript.OnStartHost(); + m_ConnectionModeScript.OnStartHostButton(); } else { @@ -187,7 +187,7 @@ private void StartClient() { if (m_ConnectionModeScript) { - m_ConnectionModeScript.OnStartClient(); + m_ConnectionModeScript.OnStartClientButton(); } else { diff --git a/testproject/Assets/Scripts/ConnectionModeScript.cs b/testproject/Assets/Scripts/ConnectionModeScript.cs index 3b225b53c7..965a351d9d 100644 --- a/testproject/Assets/Scripts/ConnectionModeScript.cs +++ b/testproject/Assets/Scripts/ConnectionModeScript.cs @@ -2,6 +2,13 @@ using UnityEngine; using Unity.Netcode; + +#if ENABLE_RELAY_SERVICE +using System; +using Unity.Services.Core; +using Unity.Services.Authentication; +#endif + /// /// Used in tandem with the ConnectModeButtons prefab asset in test project /// @@ -10,7 +17,27 @@ public class ConnectionModeScript : MonoBehaviour [SerializeField] private GameObject m_ConnectionModeButtons; + [SerializeField] + private GameObject m_AuthenticationButtons; + + [SerializeField] + private GameObject m_JoinCodeInput; + + [SerializeField] + private int m_MaxConnections = 10; + private CommandLineProcessor m_CommandLineProcessor; + + private bool m_UsingRelay = false; + +#if ENABLE_RELAY_SERVICE + [SerializeField] + private string m_RelayAllocationBasePath = "https://relay-allocations-stg.services.api.unity.com"; + + [HideInInspector] + public string RelayJoinCode { get; set; } +#endif + internal void SetCommandLineHandler(CommandLineProcessor commandLineProcessor) { m_CommandLineProcessor = commandLineProcessor; @@ -26,7 +53,6 @@ internal void SetCommandLineHandler(CommandLineProcessor commandLineProcessor) public event OnNotifyConnectionEventDelegateHandler OnNotifyConnectionEventHost; public event OnNotifyConnectionEventDelegateHandler OnNotifyConnectionEventClient; - private IEnumerator WaitForNetworkManager() { while (true) @@ -50,50 +76,200 @@ private IEnumerator WaitForNetworkManager() private void Start() { //If we have a NetworkManager instance and we are not listening and m_ConnectionModeButtons is not null then show the connection mode buttons - if (m_ConnectionModeButtons) + if (m_ConnectionModeButtons && m_AuthenticationButtons) + { +#if ENABLE_RELAY_SERVICE + if (NetworkManager.Singleton.GetComponent().Protocol == UnityTransport.ProtocolType.RelayUnityTransport) + { + m_UsingRelay = true; + m_JoinCodeInput.SetActive(true); + //If Start() is called on the first frame update, it's not likely that the AuthenticationService is going to be instantiated yet + //Moved old logic for this out to OnServicesInitialized + m_ConnectionModeButtons.SetActive(false); + m_AuthenticationButtons.SetActive(true); + } + else +#endif + { + m_JoinCodeInput.SetActive(false); + m_AuthenticationButtons.SetActive(false); + m_ConnectionModeButtons.SetActive(NetworkManager.Singleton && !NetworkManager.Singleton.IsListening); + } + } + } + + private void OnServicesInitialized() + { +#if ENABLE_RELAY_SERVICE + if (NetworkManager.Singleton.GetComponent().Protocol == UnityTransport.ProtocolType.RelayUnityTransport) { - m_ConnectionModeButtons.SetActive(NetworkManager.Singleton && !NetworkManager.Singleton.IsListening); + m_JoinCodeInput.SetActive(true); + m_ConnectionModeButtons.SetActive(false || AuthenticationService.Instance.IsSignedIn); + m_AuthenticationButtons.SetActive(NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && !AuthenticationService.Instance.IsSignedIn); } +#endif } /// /// Handles starting netcode in server mode /// - public void OnStartServer() + public void OnStartServerButton() { if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && m_ConnectionModeButtons) { - NetworkManager.Singleton.StartServer(); - OnNotifyConnectionEventServer?.Invoke(); - m_ConnectionModeButtons.SetActive(false); + if (m_UsingRelay) + { + StartCoroutine(StartRelayServer(StartServer)); + } + else + { + StartServer(); + } + } + } + + private void StartServer() + { + NetworkManager.Singleton.StartServer(); + OnNotifyConnectionEventServer?.Invoke(); + m_ConnectionModeButtons.SetActive(false); + } + + /// + /// Coroutine that handles starting MLAPI in server mode if Relay is enabled + /// + private IEnumerator StartRelayServer(Action postAllocationAction) + { +#if ENABLE_RELAY_SERVICE + m_ConnectionModeButtons.SetActive(false); + + var serverRelayUtilityTask = RelayUtility.AllocateRelayServerAndGetJoinCode(m_MaxConnections); + while (!serverRelayUtilityTask.IsCompleted) + { + yield return null; + } + if (serverRelayUtilityTask.IsFaulted) + { + Debug.LogError("Exception thrown when attempting to start Relay Server. Server not started. Exception: " + serverRelayUtilityTask.Exception.Message); + yield break; } + + var (ipv4address, port, allocationIdBytes, connectionData, key, joinCode) = serverRelayUtilityTask.Result; + + RelayJoinCode = joinCode; + + //When starting a relay server, both instances of connection data are identical. + NetworkManager.Singleton.GetComponent().SetRelayServerData(ipv4address, port, allocationIdBytes, key, connectionData); + + postAllocationAction(); +#else + yield return null; +#endif } + /// /// Handles starting netcode in host mode /// - public void OnStartHost() + public void OnStartHostButton() { if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && m_ConnectionModeButtons) { - NetworkManager.Singleton.StartHost(); - OnNotifyConnectionEventHost?.Invoke(); - m_ConnectionModeButtons.SetActive(false); + if (m_UsingRelay) + { + StartCoroutine(StartRelayServer(StartHost)); + } + else + { + StartHost(); + } } } + private void StartHost() + { + NetworkManager.Singleton.StartHost(); + OnNotifyConnectionEventHost?.Invoke(); + m_ConnectionModeButtons.SetActive(false); + } + /// /// Handles starting netcode in client mode /// - public void OnStartClient() + public void OnStartClientButton() { if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && m_ConnectionModeButtons) { - NetworkManager.Singleton.StartClient(); - OnNotifyConnectionEventClient?.Invoke(); - m_ConnectionModeButtons.SetActive(false); +#if ENABLE_RELAY_SERVICE + StartCoroutine(StartRelayClient()); +#else + StartClient(); +#endif + } + } + + private void StartClient() + { + NetworkManager.Singleton.StartClient(); + OnNotifyConnectionEventClient?.Invoke(); + m_ConnectionModeButtons.SetActive(false); + } + + + /// + /// Coroutine that kicks off Relay SDK calls to join a Relay Server instance with a join code + /// + /// + private IEnumerator StartRelayClient() + { +#if ENABLE_RELAY_SERVICE + m_ConnectionModeButtons.SetActive(false); + + //assumes that RelayJoinCodeInput populated RelayJoinCode prior to this + var clientRelayUtilityTask = RelayUtility.JoinRelayServerFromJoinCode(RelayJoinCode); + + while (!clientRelayUtilityTask.IsCompleted) + { + yield return null; + } + + if (clientRelayUtilityTask.IsFaulted) + { + Debug.LogError("Exception thrown when attempting to connect to Relay Server. Exception: " + clientRelayUtilityTask.Exception.Message); + yield break; + } + + var (ipv4address, port, allocationIdBytes, connectionData, hostConnectionData, key) = clientRelayUtilityTask.Result; + + //When connecting as a client to a relay server, connectionData and hostConnectionData are different. + NetworkManager.Singleton.GetComponent().SetRelayServerData(ipv4address, port, allocationIdBytes, key, connectionData, hostConnectionData); + + NetworkManager.Singleton.StartClient(); + OnNotifyConnectionEventClient?.Invoke(); +#else + yield return null; +#endif + } + + /// + /// Handles authenticating UnityServices, needed for Relay + /// + public async void OnSignIn() + { +#if ENABLE_RELAY_SERVICE + await UnityServices.InitializeAsync(); + OnServicesInitialized(); + await AuthenticationService.Instance.SignInAnonymouslyAsync(); + + Debug.Log($"Logging in with PlayerID {AuthenticationService.Instance.PlayerId}"); + + if (AuthenticationService.Instance.IsSignedIn) + { + m_ConnectionModeButtons.SetActive(true); + m_AuthenticationButtons.SetActive(false); } +#endif } public void Reset() diff --git a/testproject/Assets/Scripts/RelayJoinCodeInput.cs b/testproject/Assets/Scripts/RelayJoinCodeInput.cs new file mode 100644 index 0000000000..1c18f76e2e --- /dev/null +++ b/testproject/Assets/Scripts/RelayJoinCodeInput.cs @@ -0,0 +1,30 @@ +using UnityEngine; +using UnityEngine.UI; + +public class RelayJoinCodeInput : MonoBehaviour +{ + public ConnectionModeScript ConnectionScript; + private InputField m_TextInput; + + private void Start() + { + m_TextInput = GetComponent(); + } + + private void Update() + { + if (m_TextInput.IsInteractable()) + { + if (!string.IsNullOrEmpty(ConnectionScript.RelayJoinCode)) + { + m_TextInput.text = ConnectionScript.RelayJoinCode; + m_TextInput.readOnly = true; + } + } + } + + public void SetJoinCode() + { + ConnectionScript.RelayJoinCode = m_TextInput.text; + } +} diff --git a/testproject/Assets/Scripts/RelayJoinCodeInput.cs.meta b/testproject/Assets/Scripts/RelayJoinCodeInput.cs.meta new file mode 100644 index 0000000000..981841abab --- /dev/null +++ b/testproject/Assets/Scripts/RelayJoinCodeInput.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 903872102732f5c4cbdd3863de5dbc97 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Scripts/RelayUtility.cs b/testproject/Assets/Scripts/RelayUtility.cs new file mode 100644 index 0000000000..435842c2e2 --- /dev/null +++ b/testproject/Assets/Scripts/RelayUtility.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using UnityEngine; +using Unity.Services.Relay; +using Unity.Services.Relay.Models; +using System; + +public class RelayUtility +{ + public static async Task<(string ipv4address, ushort port, byte[] allocationIdBytes, byte[] connectionData, byte[] key, string joinCode)> AllocateRelayServerAndGetJoinCode(int maxConnections, string region = null) + { + Allocation allocation; + string createJoinCode; + try + { + allocation = await Relay.Instance.CreateAllocationAsync(maxConnections, region); + } + catch (Exception e) + { + Debug.LogError($"Relay create allocation request failed {e.Message}"); + throw; + } + + Debug.Log($"server: {allocation.ConnectionData[0]} {allocation.ConnectionData[1]}"); + Debug.Log($"server: {allocation.AllocationId}"); + + try + { + createJoinCode = await Relay.Instance.GetJoinCodeAsync(allocation.AllocationId); + } + catch + { + Debug.LogError("Relay create join code request failed"); + throw; + } + + return (allocation.RelayServer.IpV4, (ushort)allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.Key, createJoinCode); + } + + public static async Task<(string ipv4address, ushort port, byte[] allocationIdBytes, byte[] connectionData, byte[] hostConnectionData, byte[] key)> JoinRelayServerFromJoinCode(string joinCode) + { + JoinAllocation allocation; + try + { + allocation = await Relay.Instance.JoinAllocationAsync(joinCode); + } + catch + { + Debug.LogError("Relay create join code request failed"); + throw; + } + + Debug.Log($"client: {allocation.ConnectionData[0]} {allocation.ConnectionData[1]}"); + Debug.Log($"host: {allocation.HostConnectionData[0]} {allocation.HostConnectionData[1]}"); + Debug.Log($"client: {allocation.AllocationId}"); + + return (allocation.RelayServer.IpV4, (ushort)allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.HostConnectionData, allocation.Key); + } +} diff --git a/testproject/Assets/Scripts/RelayUtility.cs.meta b/testproject/Assets/Scripts/RelayUtility.cs.meta new file mode 100644 index 0000000000..b7879fb3a2 --- /dev/null +++ b/testproject/Assets/Scripts/RelayUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 821a03e0e4496574c8ca6a558047e944 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Scripts/UIController.cs b/testproject/Assets/Scripts/UIController.cs index 8b71f5b856..913e365835 100644 --- a/testproject/Assets/Scripts/UIController.cs +++ b/testproject/Assets/Scripts/UIController.cs @@ -1,10 +1,29 @@ using UnityEngine; using Unity.Netcode; +#if ENABLE_RELAY_SERVICE +using Unity.Services.Core; +using Unity.Services.Authentication; +#endif public class UIController : MonoBehaviour { public NetworkManager NetworkManager; public GameObject ButtonsRoot; + public GameObject AuthButton; + public GameObject JoinCode; + + public UnityTransport Transport; + + private void Awake() + { +#if ENABLE_RELAY_SERVICE + if (Transport.Protocol == UnityTransport.ProtocolType.RelayUnityTransport) + { + HideButtons(); + JoinCode.SetActive(false); + } +#endif + } public void StartServer() { @@ -28,4 +47,22 @@ private void HideButtons() { ButtonsRoot.SetActive(false); } + + + public async void OnSignIn() + { +#if ENABLE_RELAY_SERVICE + await UnityServices.InitializeAsync(); + Debug.Log("OnSignIn"); + await AuthenticationService.Instance.SignInAnonymouslyAsync(); + Debug.Log($"Logging in with PlayerID {AuthenticationService.Instance.PlayerId}"); + + if (AuthenticationService.Instance.IsSignedIn) + { + ButtonsRoot.SetActive(true); + JoinCode.SetActive(true); + AuthButton.SetActive(false); + } +#endif + } } diff --git a/testproject/Assets/Scripts/testproject.asmdef b/testproject/Assets/Scripts/testproject.asmdef index 7dd825f751..b10fb913ad 100644 --- a/testproject/Assets/Scripts/testproject.asmdef +++ b/testproject/Assets/Scripts/testproject.asmdef @@ -1,8 +1,28 @@ { "name": "TestProject", + "rootNamespace": "", "references": [ "Unity.Netcode.Runtime", "Unity.Netcode.Editor", - "Unity.Netcode.Components" - ] + "Unity.Netcode.Components", + "Unity.Netcode.Adapter.UTP", + "Unity.Services.Authentication", + "Unity.Services.Core", + "Unity.Services.Relay" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.services.relay", + "expression": "0.0.1-preview.3", + "define": "ENABLE_RELAY_SERVICE" + } + ], + "noEngineReferences": false } \ No newline at end of file diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 345ff22e9f..ad9e383464 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,16 +1,21 @@ { "dependencies": { - "com.unity.collab-proxy": "1.5.7", - "com.unity.ide.rider": "3.0.5", - "com.unity.ide.visualstudio": "2.0.8", + "com.unity.collab-proxy": "1.9.0", + "com.unity.ide.rider": "3.0.7", + "com.unity.ide.visualstudio": "2.0.11", "com.unity.ide.vscode": "1.2.3", + "com.unity.mathematics": "1.2.1", + "com.unity.netcode.adapter.utp": "file:../../com.unity.netcode.adapter.utp", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", - "com.unity.multiplayer.transport.utp": "file:../../com.unity.multiplayer.transport.utp", - "com.unity.package-validation-suite": "0.19.2-preview", - "com.unity.test-framework": "1.1.27", - "com.unity.test-framework.performance": "2.3.1-preview", + "com.unity.package-validation-suite": "0.21.0-preview", + "com.unity.services.authentication": "1.0.0-pre.4", + "com.unity.services.core": "1.1.0-pre.8", + "com.unity.services.relay": "1.0.1-pre.1", + "com.unity.test-framework": "1.1.29", + "com.unity.test-framework.performance": "2.8.0-preview", "com.unity.textmeshpro": "3.0.6", - "com.unity.timeline": "1.5.2", + "com.unity.timeline": "1.6.2", + "com.unity.transport": "1.0.0-pre.4", "com.unity.ugui": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", @@ -46,6 +51,6 @@ }, "testables": [ "com.unity.netcode.gameobjects", - "com.unity.multiplayer.transport.utp" + "com.unity.netcode.adapter.utp" ] } diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 9d043edc10..0706f2bbb7 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -1,30 +1,28 @@ { "dependencies": { "com.unity.burst": { - "version": "1.3.2", - "depth": 2, + "version": "1.5.5", + "depth": 1, "source": "registry", "dependencies": { - "com.unity.mathematics": "1.1.0" + "com.unity.mathematics": "1.2.1" }, "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "1.5.7", + "version": "1.9.0", "depth": 0, "source": "registry", - "dependencies": { - "com.unity.nuget.newtonsoft-json": "2.0.0" - }, + "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.collections": { - "version": "0.12.0-preview.13", + "version": "1.0.0-pre.5", "depth": 1, "source": "registry", "dependencies": { - "com.unity.test-framework.performance": "2.3.1-preview", - "com.unity.burst": "1.3.2" + "com.unity.burst": "1.5.4", + "com.unity.test-framework": "1.1.22" }, "url": "https://packages.unity.com" }, @@ -36,14 +34,16 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.5", + "version": "3.0.7", "depth": 0, "source": "registry", - "dependencies": {}, + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.9", + "version": "2.0.11", "depth": 0, "source": "registry", "dependencies": { @@ -58,31 +58,20 @@ "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.jobs": { - "version": "0.2.10-preview.13", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.collections": "0.9.0-preview.6", - "com.unity.mathematics": "1.1.0" - }, - "url": "https://packages.unity.com" - }, "com.unity.mathematics": { - "version": "1.1.0", - "depth": 2, + "version": "1.2.1", + "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.multiplayer.transport.utp": { - "version": "file:../../com.unity.multiplayer.transport.utp", + "com.unity.netcode.adapter.utp": { + "version": "file:../../com.unity.netcode.adapter.utp", "depth": 0, "source": "local", "dependencies": { "com.unity.netcode.gameobjects": "0.0.1-preview.1", - "com.unity.transport": "0.4.1-preview.1", - "com.unity.jobs": "0.2.10-preview.13" + "com.unity.transport": "1.0.0-pre.4" } }, "com.unity.netcode.gameobjects": { @@ -93,7 +82,7 @@ "com.unity.modules.ai": "1.0.0", "com.unity.modules.animation": "1.0.0", "com.unity.nuget.mono-cecil": "1.10.1-preview.1", - "com.unity.collections": "0.12.0-preview.13" + "com.unity.collections": "1.0.0-pre.5" } }, "com.unity.nuget.mono-cecil": { @@ -111,7 +100,7 @@ "url": "https://packages.unity.com" }, "com.unity.package-validation-suite": { - "version": "0.19.2-preview", + "version": "0.21.0-preview", "depth": 0, "source": "registry", "dependencies": { @@ -119,8 +108,44 @@ }, "url": "https://packages.unity.com" }, + "com.unity.services.authentication": { + "version": "1.0.0-pre.4", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.nuget.newtonsoft-json": "2.0.0", + "com.unity.services.core": "1.1.0-pre.8", + "com.unity.modules.unitywebrequest": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.core": { + "version": "1.1.0-pre.8", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.relay": { + "version": "1.0.1-pre.1", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.services.core": "1.1.0-pre.8", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.nuget.newtonsoft-json": "2.0.0", + "com.unity.services.authentication": "1.0.0-pre.4" + }, + "url": "https://packages.unity.com" + }, "com.unity.test-framework": { - "version": "1.1.27", + "version": "1.1.29", "depth": 0, "source": "registry", "dependencies": { @@ -131,12 +156,12 @@ "url": "https://packages.unity.com" }, "com.unity.test-framework.performance": { - "version": "2.3.1-preview", + "version": "2.8.0-preview", "depth": 0, "source": "registry", "dependencies": { "com.unity.test-framework": "1.1.0", - "com.unity.nuget.newtonsoft-json": "2.0.0-preview" + "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, @@ -150,7 +175,7 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.5.2", + "version": "1.6.2", "depth": 0, "source": "registry", "dependencies": { @@ -162,13 +187,13 @@ "url": "https://packages.unity.com" }, "com.unity.transport": { - "version": "0.4.1-preview.1", - "depth": 1, + "version": "1.0.0-pre.4", + "depth": 0, "source": "registry", "dependencies": { - "com.unity.burst": "1.3.0", - "com.unity.collections": "0.12.0-preview.13", - "com.unity.mathematics": "1.1.0" + "com.unity.collections": "1.0.0-pre.5", + "com.unity.burst": "1.5.5", + "com.unity.mathematics": "1.2.1" }, "url": "https://packages.unity.com" }, diff --git a/testproject/ProjectSettings/PackageManagerSettings.asset b/testproject/ProjectSettings/PackageManagerSettings.asset index 102c612892..b01b2f8da9 100644 --- a/testproject/ProjectSettings/PackageManagerSettings.asset +++ b/testproject/ProjectSettings/PackageManagerSettings.asset @@ -12,12 +12,11 @@ MonoBehaviour: m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: m_EditorClassIdentifier: - m_EnablePreReleasePackages: 0 - m_EnablePackageDependencies: 0 + m_EnablePreviewPackages: 1 + m_EnablePackageDependencies: 1 m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 - m_SeeAllPackageVersions: 0 - oneTimeWarningShown: 0 + oneTimeWarningShown: 1 m_Registries: - m_Id: main m_Name: @@ -42,4 +41,3 @@ MonoBehaviour: m_Scopes: - m_SelectedScopeIndex: 0 - m_LoadAssets: 0 diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 445963c62e..519d9b689f 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -12,12 +12,12 @@ PlayerSettings: targetDevice: 2 useOnDemandResources: 0 accelerometerFrequency: 60 - companyName: DefaultCompany + companyName: Unity Technologies productName: testproject defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} - m_ShowUnitySplashScreen: 0 + m_ShowUnitySplashScreen: 1 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 @@ -146,7 +146,7 @@ PlayerSettings: androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 applicationIdentifier: - Standalone: com.DefaultCompany.testproject + Standalone: com.UnityTechnologies.testproject buildNumber: Standalone: 0 iPhone: 0 @@ -579,7 +579,8 @@ PlayerSettings: webGLLinkerTarget: 1 webGLThreadsSupport: 0 webGLDecompressionFallback: 0 - scriptingDefineSymbols: {} + scriptingDefineSymbols: + 1: additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: {} @@ -655,7 +656,14 @@ PlayerSettings: XboxOneOverrideIdentityPublisher: vrEditorSettings: {} cloudServicesEnabled: + Analytics: 0 + Build: 0 + Collab: 0 + Game Performance: 0 + Purchasing: 0 + UDP: 0 UNet: 1 + Unity Ads: 0 luminIcon: m_Name: m_ModelFolderPath: @@ -672,8 +680,8 @@ PlayerSettings: cloudProjectId: framebufferDepthMemorylessMode: 0 qualitySettingsNames: [] - projectName: - organizationId: + projectName: relay-stg + organizationId: relay-stg cloudEnabled: 0 legacyClampBlendShapeWeights: 0 virtualTexturingSupportEnabled: 0