diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index d0606897ec..74f8abefb5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -245,7 +245,7 @@ public ulong GetConfig(bool cache = true) writer.WriteUInt32Packed(sortedEntry.Key); } } - + writer.WriteBool(ConnectionApproval); writer.WriteBool(EnableNetworkVariable); writer.WriteBool(ForceSamePrefabs); writer.WriteBool(EnableSceneManagement); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs index 4ea516068c..d8318847a5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs @@ -31,7 +31,9 @@ public void HandleConnectionRequest(ulong clientId, Stream stream) NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. The configuration between the server and client does not match"); } - NetworkManager.DisconnectClient(clientId); + // Treat this similar to a client that is not approved (remove from pending and disconnect at transport layer) + NetworkManager.PendingClients.Remove(clientId); + NetworkManager.NetworkConfig.NetworkTransport.DisconnectRemoteClient(clientId); return; } diff --git a/testproject/Assets/Scripts/ConnectionModeScript.cs b/testproject/Assets/Scripts/ConnectionModeScript.cs index cb87892cec..3b225b53c7 100644 --- a/testproject/Assets/Scripts/ConnectionModeScript.cs +++ b/testproject/Assets/Scripts/ConnectionModeScript.cs @@ -95,4 +95,12 @@ public void OnStartClient() m_ConnectionModeButtons.SetActive(false); } } + + public void Reset() + { + if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && m_ConnectionModeButtons) + { + m_ConnectionModeButtons.SetActive(true); + } + } } diff --git a/testproject/Assets/Tests/Manual/ConnectionApproval/ConnectionApprovalTest.unity b/testproject/Assets/Tests/Manual/ConnectionApproval/ConnectionApprovalTest.unity index 6bcdf17aa8..591447cf8b 100644 --- a/testproject/Assets/Tests/Manual/ConnectionApproval/ConnectionApprovalTest.unity +++ b/testproject/Assets/Tests/Manual/ConnectionApproval/ConnectionApprovalTest.unity @@ -38,7 +38,7 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - 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: @@ -143,7 +143,7 @@ PrefabInstance: - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} propertyPath: m_RootOrder - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} @@ -300,7 +300,7 @@ RectTransform: - {fileID: 1033031600} - {fileID: 839387550} m_Father: {fileID: 1907934187} - m_RootOrder: 4 + m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} @@ -420,6 +420,87 @@ MonoBehaviour: m_ConnectionMessageToDisplay: {fileID: 1394268806} m_SimulateFailure: {fileID: 344942972} m_PlayerPrefabOverride: {fileID: 1493186929} + m_ClientDisconnectButton: {fileID: 1357994470} + m_ConnectionModeButtons: {fileID: 1931383346} +--- !u!1 &609255385 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 609255386} + - component: {fileID: 609255388} + - component: {fileID: 609255387} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &609255386 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 609255385} + 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: 1357994469} + 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!114 &609255387 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 609255385} + 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: Disconnect +--- !u!222 &609255388 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 609255385} + m_CullTransparentMesh: 1 --- !u!1001 &647084986 PrefabInstance: m_ObjectHideFlags: 0 @@ -525,9 +606,6 @@ MonoBehaviour: NetworkConfig: ProtocolVersion: 0 NetworkTransport: {fileID: 697639107} - RegisteredScenes: - - ConnectionApprovalTest - AllowRuntimeSceneChanges: 0 PlayerPrefab: {fileID: 4079352819444256614, guid: c16f03336b6104576a565ef79ad643c0, type: 3} NetworkPrefabs: @@ -571,12 +649,7 @@ MonoBehaviour: ConnectAddress: 127.0.0.1 ConnectPort: 7777 ServerListenPort: 7777 - ServerWebsocketListenPort: 8887 - SupportWebsocket: 0 Channels: [] - UseNetcodeRelay: 0 - NetcodeRelayAddress: 127.0.0.1 - NetcodeRelayPort: 8888 MessageSendMode: 0 --- !u!4 &697639108 Transform: @@ -918,7 +991,7 @@ RectTransform: m_LocalScale: {x: 0.7183, y: 0.7183, z: 0.7183} m_Children: [] m_Father: {fileID: 1907934187} - m_RootOrder: 1 + m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0} m_AnchorMax: {x: 1, y: 0} @@ -967,6 +1040,140 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1174150577} m_CullTransparentMesh: 0 +--- !u!1 &1357994468 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1357994469} + - component: {fileID: 1357994472} + - component: {fileID: 1357994471} + - component: {fileID: 1357994470} + m_Layer: 5 + m_Name: ClientDisconnect + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1357994469 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1357994468} + 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: 609255386} + m_Father: {fileID: 1907934187} + 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: 260} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1357994470 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1357994468} + 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: 1357994471} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 606367640} + m_TargetAssemblyTypeName: TestProject.ManualTests.ConnectionApprovalComponent, + TestProject.ManualTests + m_MethodName: OnDisconnectClient + 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!114 &1357994471 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1357994468} + 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!222 &1357994472 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1357994468} + m_CullTransparentMesh: 1 --- !u!1 &1394268804 GameObject: m_ObjectHideFlags: 0 @@ -997,12 +1204,12 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1907934187} - m_RootOrder: 3 + m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} - m_AnchoredPosition: {x: 0, y: 30} - m_SizeDelta: {x: 200, y: 30} + m_AnchoredPosition: {x: 0, y: 109} + m_SizeDelta: {x: 400, y: 200} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1394268806 MonoBehaviour: @@ -1026,12 +1233,12 @@ MonoBehaviour: m_Calls: [] m_FontData: m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} - m_FontSize: 22 + m_FontSize: 16 m_FontStyle: 1 - m_BestFit: 1 - m_MinSize: 2 + m_BestFit: 0 + m_MinSize: 0 m_MaxSize: 40 - m_Alignment: 4 + m_Alignment: 7 m_AlignByGeometry: 0 m_RichText: 1 m_HorizontalOverflow: 0 @@ -1077,7 +1284,7 @@ RectTransform: - {fileID: 1890361339} - {fileID: 1760562150} m_Father: {fileID: 1907934187} - m_RootOrder: 5 + m_RootOrder: 6 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} @@ -1152,7 +1359,7 @@ PrefabInstance: - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, type: 3} propertyPath: m_RootOrder - value: 2 + value: 3 objectReference: {fileID: 0} - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, type: 3} @@ -1754,6 +1961,7 @@ RectTransform: m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0, y: 0, z: 0} m_Children: + - {fileID: 1357994469} - {fileID: 274528836} - {fileID: 1174150578} - {fileID: 1569463775} @@ -1768,3 +1976,15 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} +--- !u!114 &1931383346 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 4850072633501053442, guid: d725b5588e1b956458798319e6541d84, + type: 3} + m_PrefabInstance: {fileID: 1569463774} + 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: diff --git a/testproject/Assets/Tests/Manual/Scripts/ConnectionApprovalComponent.cs b/testproject/Assets/Tests/Manual/Scripts/ConnectionApprovalComponent.cs index 8fd734007a..7ba7fefc5e 100644 --- a/testproject/Assets/Tests/Manual/Scripts/ConnectionApprovalComponent.cs +++ b/testproject/Assets/Tests/Manual/Scripts/ConnectionApprovalComponent.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.UI; @@ -6,6 +7,9 @@ namespace TestProject.ManualTests { + /// + /// This component demonstrates how to use the Netcode for GameObjects connection approval feature + /// public class ConnectionApprovalComponent : NetworkBehaviour { [SerializeField] @@ -23,11 +27,22 @@ public class ConnectionApprovalComponent : NetworkBehaviour [SerializeField] private Toggle m_PlayerPrefabOverride; + [SerializeField] + private Button m_ClientDisconnectButton; + [SerializeField] + private ConnectionModeScript m_ConnectionModeButtons; - private void Start() + private class MessageEntry { + public string Message; + public float TimeOut; + } + private List m_Messages = new List(); + + private void Start() + { if (m_PlayerPrefabOverride) { m_PlayerPrefabOverride.gameObject.SetActive(false); @@ -43,6 +58,11 @@ private void Start() m_ConnectionMessageToDisplay.gameObject.SetActive(false); } + if (m_ClientDisconnectButton) + { + m_ClientDisconnectButton.gameObject.SetActive(false); + } + if (NetworkManager != null && NetworkManager.NetworkConfig.ConnectionApproval) { NetworkManager.ConnectionApprovalCallback += ConnectionApprovalCallback; @@ -55,11 +75,50 @@ private void Start() { Debug.LogError($"You need to set the {nameof(m_ApprovalToken)} to a value first!"); } + } + + NetworkManager.OnClientDisconnectCallback += NetworkManager_OnClientDisconnectCallback; + NetworkManager.OnClientConnectedCallback += NetworkManager_OnClientConnectedCallback; + } - NetworkManager.OnClientDisconnectCallback += NetworkManager_OnClientDisconnectCallback; + /// + /// When a client connects we display a message and if we are not the server + /// we display a disconnect button for ease of testing. + /// + private void NetworkManager_OnClientConnectedCallback(ulong clientId) + { + if (m_ClientDisconnectButton) + { + m_ClientDisconnectButton.gameObject.SetActive(!IsServer); + } + + AddNewMessage($"Client {clientId} was connected."); + } + + /// + /// When a client is disconnected we display a message and if we + /// are not listening and not the server we reset the UI Connection + /// mode buttons + /// + private void NetworkManager_OnClientDisconnectCallback(ulong clientId) + { + + AddNewMessage($"Client {clientId} was disconnected!"); + + if (!NetworkManager.IsListening && !NetworkManager.IsServer) + { + m_ConnectionModeButtons.Reset(); + } + + if (m_ClientDisconnectButton) + { + m_ClientDisconnectButton.gameObject.SetActive(false); } } + /// + /// Just display certain check boxes only when we are in a network session + /// public override void OnNetworkSpawn() { if (m_SimulateFailure) @@ -73,56 +132,108 @@ public override void OnNetworkSpawn() } } - private void NetworkManager_OnClientDisconnectCallback(ulong obj) + /// + /// Used for the client when the disconnect button is pressed + /// + public void OnDisconnectClient() { - Debug.Log($"Client {obj} connected!"); + if ( NetworkManager != null && NetworkManager.IsListening && !NetworkManager.IsServer) + { + NetworkManager.Shutdown(); + m_ConnectionModeButtons.Reset(); + if (m_ClientDisconnectButton) + { + m_ClientDisconnectButton.gameObject.SetActive(false); + } + } } - private void ConnectionApprovalCallback(byte[] arg1, ulong arg2, NetworkManager.ConnectionApprovedDelegate arg3) + /// + /// Invoked only on the server, this will handle the various connection approval combinations + /// + /// key or password to get approval + /// client identifier being approved + /// callback that should be invoked once it is determined if client is approved or not + private void ConnectionApprovalCallback(byte[] dataToken, ulong clientId, NetworkManager.ConnectionApprovedDelegate aprovalCallback) { - string approvalToken = Encoding.ASCII.GetString(arg1); + string approvalToken = Encoding.ASCII.GetString(dataToken); var isTokenValid = approvalToken == m_ApprovalToken; - if (m_SimulateFailure && m_SimulateFailure.isOn && IsServer && arg2 != NetworkManager.LocalClientId) + if (m_SimulateFailure && m_SimulateFailure.isOn && IsServer && clientId != NetworkManager.LocalClientId) { isTokenValid = false; } - if (isTokenValid) + if (m_GlobalObjectIdHashOverride != 0 && m_PlayerPrefabOverride && m_PlayerPrefabOverride.isOn) { - if (m_GlobalObjectIdHashOverride != 0 && m_PlayerPrefabOverride && m_PlayerPrefabOverride.isOn) - { - arg3.Invoke(true, m_GlobalObjectIdHashOverride, true, null, null); - } - else - { - arg3.Invoke(true, null, true, null, null); - } + aprovalCallback.Invoke(true, m_GlobalObjectIdHashOverride, isTokenValid, null, null); } else { - NetworkManager.DisconnectClient(arg2); - Debug.LogWarning($"User id {arg2} was disconnected due to failed connection approval!"); + aprovalCallback.Invoke(true, null, isTokenValid, null, null); } - if (m_ConnectionMessageToDisplay && arg2 != NetworkManager.LocalClientId) + + if (m_ConnectionMessageToDisplay) { if (isTokenValid) { - m_ConnectionMessageToDisplay.text = $"Client id {arg2} is authorized!"; + AddNewMessage($"Client id {clientId} is authorized!"); } else { - m_ConnectionMessageToDisplay.text = $"Client id {arg2} failed authorization!"; + AddNewMessage($"Client id {clientId} failed authorization!"); } m_ConnectionMessageToDisplay.gameObject.SetActive(true); - StartCoroutine(WaitToHideConnectionText()); } } - private IEnumerator WaitToHideConnectionText() + /// + /// Adds a new message to be displayed and if our display coroutine is not running start it. + /// + /// message to add to the list of messages to be displayed + private void AddNewMessage(string msg) { - yield return new WaitForSeconds(5); + m_Messages.Add(new MessageEntry() { Message = msg, TimeOut = Time.realtimeSinceStartup + 8.0f }); + if (!m_ConnectionMessageToDisplay.gameObject.activeInHierarchy) + { + StartCoroutine(DisplayMessatesUntilEmpty()); + if (m_ConnectionMessageToDisplay) + { + m_ConnectionMessageToDisplay.gameObject.SetActive(true); + } + } + } + + /// + /// Coroutine that displays messages until there are no more messages to be displayed. + /// + /// + private IEnumerator DisplayMessatesUntilEmpty() + { + var messagesToRemove = new List(); + while (m_Messages.Count > 0) + { + m_ConnectionMessageToDisplay.text = string.Empty; + foreach (var message in m_Messages) + { + if (message.TimeOut > Time.realtimeSinceStartup) + { + m_ConnectionMessageToDisplay.text += message.Message + "\n"; + } + else + { + messagesToRemove.Add(message); + } + } + yield return new WaitForSeconds(0.5f); + foreach (var message in messagesToRemove) + { + m_Messages.Remove(message); + } + messagesToRemove.Clear(); + } + if (m_ConnectionMessageToDisplay) { m_ConnectionMessageToDisplay.gameObject.SetActive(false); diff --git a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs index f506dcb425..d96c970b6d 100644 --- a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs +++ b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs @@ -241,6 +241,50 @@ private void Server_OnClientConnectedCallback(ulong clientId) m_ServerClientConnectedInvocations++; } + + private int m_ServerClientDisconnectedInvocations; + + /// + /// Tests that clients are disconnected when their ConnectionApproval setting is mismatched with the host-server + /// and when scene management is enabled and disabled + /// + /// + [UnityTest] + public IEnumerator ConnectionApprovalMismatchTest([Values(true, false)] bool enableSceneManagement, [Values(true,false)] bool connectionApproval) + { + m_ServerClientDisconnectedInvocations = 0; + + // Create Host and (numClients) clients + Assert.True(MultiInstanceHelpers.Create(3, out NetworkManager server, out NetworkManager[] clients)); + + server.NetworkConfig.EnableSceneManagement = enableSceneManagement; + server.OnClientDisconnectCallback += Server_OnClientDisconnectedCallback; + server.NetworkConfig.ConnectionApproval = connectionApproval; + + foreach (var client in clients) + { + client.NetworkConfig.EnableSceneManagement = enableSceneManagement; + client.NetworkConfig.ConnectionApproval = !connectionApproval; + } + + // Start the instances + if (!MultiInstanceHelpers.Start(true, server, clients)) + { + Assert.Fail("Failed to start instances"); + } + + var nextFrameNumber = Time.frameCount + 5; + yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + + Assert.True(m_ServerClientDisconnectedInvocations == 3); + } + + private void Server_OnClientDisconnectedCallback(ulong clientId) + { + m_ServerClientDisconnectedInvocations++; + } + + [TearDown] public void TearDown() {