Skip to content

Commit eaf781e

Browse files
jeffreyrainy0xFA11
andauthored
fix: preventing issues with game code registering multiple ConnectionApprovalCallbacks [MTT-3496] (#1972)
Co-authored-by: Fatih Mar <[email protected]>
1 parent 6d54e28 commit eaf781e

File tree

6 files changed

+150
-68
lines changed

6 files changed

+150
-68
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ Additional documentation and release notes are available at [Multiplayer Documen
1212

1313
### Changed
1414

15+
- (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now a `Func<>` taking `ConnectionApprovalRequest` in and returning `ConnectionApprovalResponse` back out (#1972)
16+
1517
### Removed
1618

1719
### Fixed
1820

21+
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
1922
- Fixed endless dialog boxes when adding a NetworkBehaviour to a NetworkManager or vice-versa (#1947)
2023

2124
## [1.0.0-pre.9] - 2022-05-10

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 86 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,14 @@ public GameObject GetNetworkPrefabOverride(GameObject gameObject)
206206
/// <summary>
207207
/// Gets or sets if the application should be set to run in background
208208
/// </summary>
209-
[HideInInspector] public bool RunInBackground = true;
209+
[HideInInspector]
210+
public bool RunInBackground = true;
210211

211212
/// <summary>
212213
/// The log level to use
213214
/// </summary>
214-
[HideInInspector] public LogLevel LogLevel = LogLevel.Normal;
215+
[HideInInspector]
216+
public LogLevel LogLevel = LogLevel.Normal;
215217

216218
/// <summary>
217219
/// The singleton instance of the NetworkManager
@@ -358,26 +360,59 @@ public IReadOnlyList<ulong> ConnectedClientsIds
358360
public event Action OnServerStarted = null;
359361

360362
/// <summary>
361-
/// Delegate type called when connection has been approved. This only has to be set on the server.
363+
/// Connection Approval Response
362364
/// </summary>
363-
/// <param name="createPlayerObject">If true, a player object will be created. Otherwise the client will have no object.</param>
364-
/// <param name="playerPrefabHash">The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.</param>
365-
/// <param name="approved">Whether or not the client was approved</param>
366-
/// <param name="position">The position to spawn the client at. If null, the prefab position is used.</param>
367-
/// <param name="rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
368-
public delegate void ConnectionApprovedDelegate(bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation);
365+
/// <param name="Approved">Whether or not the client was approved</param>
366+
/// <param name="CreatePlayerObject">If true, a player object will be created. Otherwise the client will have no object.</param>
367+
/// <param name="PlayerPrefabHash">The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.</param>
368+
/// <param name="Position">The position to spawn the client at. If null, the prefab position is used.</param>
369+
/// <param name="Rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
370+
public struct ConnectionApprovalResponse
371+
{
372+
public bool Approved;
373+
public bool CreatePlayerObject;
374+
public uint? PlayerPrefabHash;
375+
public Vector3? Position;
376+
public Quaternion? Rotation;
377+
}
378+
379+
/// <summary>
380+
/// Connection Approval Request
381+
/// </summary>
382+
/// <param name="Payload">The connection data payload</param>
383+
/// <param name="ClientNetworkId">The Network Id of the client we are about to handle</param>
384+
public struct ConnectionApprovalRequest
385+
{
386+
public byte[] Payload;
387+
public ulong ClientNetworkId;
388+
}
369389

370390
/// <summary>
371-
/// The callback to invoke during connection approval
391+
/// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection
372392
/// </summary>
373-
public event Action<byte[], ulong, ConnectionApprovedDelegate> ConnectionApprovalCallback = null;
393+
public Func<ConnectionApprovalRequest, ConnectionApprovalResponse> ConnectionApprovalCallback
394+
{
395+
get => m_ConnectionApprovalCallback;
396+
set
397+
{
398+
if (value != null && value.GetInvocationList().Length > 1)
399+
{
400+
throw new InvalidOperationException($"Only one {nameof(ConnectionApprovalCallback)} can be registered at a time.");
401+
}
402+
else
403+
{
404+
m_ConnectionApprovalCallback = value;
405+
}
406+
}
407+
}
374408

375-
internal void InvokeConnectionApproval(byte[] payload, ulong clientId, ConnectionApprovedDelegate action) => ConnectionApprovalCallback?.Invoke(payload, clientId, action);
409+
private Func<ConnectionApprovalRequest, ConnectionApprovalResponse> m_ConnectionApprovalCallback;
376410

377411
/// <summary>
378412
/// The current NetworkConfig
379413
/// </summary>
380-
[HideInInspector] public NetworkConfig NetworkConfig;
414+
[HideInInspector]
415+
public NetworkConfig NetworkConfig;
381416

382417
/// <summary>
383418
/// The current host name we are connected to, used to validate certificate
@@ -970,27 +1005,28 @@ public bool StartHost()
9701005
IsClient = true;
9711006
IsListening = true;
9721007

973-
if (NetworkConfig.ConnectionApproval)
1008+
if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
9741009
{
975-
InvokeConnectionApproval(NetworkConfig.ConnectionData, ServerClientId,
976-
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
1010+
var response = ConnectionApprovalCallback(new ConnectionApprovalRequest { Payload = NetworkConfig.ConnectionData, ClientNetworkId = ServerClientId });
1011+
if (!response.Approved)
1012+
{
1013+
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
9771014
{
978-
// You cannot decline the local server. Force approved to true
979-
if (!approved)
980-
{
981-
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
982-
{
983-
NetworkLog.LogWarning(
984-
"You cannot decline the host connection. The connection was automatically approved.");
985-
}
986-
}
1015+
NetworkLog.LogWarning("You cannot decline the host connection. The connection was automatically approved.");
1016+
}
1017+
}
9871018

988-
HandleApproval(ServerClientId, createPlayerObject, playerPrefabHash, true, position, rotation);
989-
});
1019+
response.Approved = true;
1020+
HandleConnectionApproval(ServerClientId, response);
9901021
}
9911022
else
9921023
{
993-
HandleApproval(ServerClientId, NetworkConfig.PlayerPrefab != null, null, true, null, null);
1024+
var response = new ConnectionApprovalResponse
1025+
{
1026+
Approved = true,
1027+
CreatePlayerObject = NetworkConfig.PlayerPrefab != null
1028+
};
1029+
HandleConnectionApproval(ServerClientId, response);
9941030
}
9951031

9961032
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
@@ -1818,15 +1854,11 @@ private void SyncTime()
18181854
/// <summary>
18191855
/// Server Side: Handles the approval of a client
18201856
/// </summary>
1821-
/// <param name="ownerClientId">client being approved</param>
1822-
/// <param name="createPlayerObject">whether we want to create a player or not</param>
1823-
/// <param name="playerPrefabHash">the GlobalObjectIdHash value for the Network Prefab to create as the player</param>
1824-
/// <param name="approved">Is the player approved or not?</param>
1825-
/// <param name="position">Used when createPlayerObject is true, position of the player when spawned </param>
1826-
/// <param name="rotation">Used when createPlayerObject is true, rotation of the player when spawned</param>
1827-
internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation)
1857+
/// <param name="ownerClientId">The Network Id of the client being approved</param>
1858+
/// <param name="response">The response to allow the player in or not, with its parameters</param>
1859+
internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalResponse response)
18281860
{
1829-
if (approved)
1861+
if (response.Approved)
18301862
{
18311863
// Inform new client it got approved
18321864
PendingClients.Remove(ownerClientId);
@@ -1836,10 +1868,23 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint?
18361868
m_ConnectedClientsList.Add(client);
18371869
m_ConnectedClientIds.Add(client.ClientId);
18381870

1839-
if (createPlayerObject)
1871+
if (response.CreatePlayerObject)
18401872
{
1841-
var networkObject = SpawnManager.CreateLocalNetworkObject(false, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, null, null, position, rotation);
1842-
SpawnManager.SpawnNetworkObjectLocally(networkObject, SpawnManager.GetNetworkObjectId(), false, true, ownerClientId, false);
1873+
var networkObject = SpawnManager.CreateLocalNetworkObject(
1874+
isSceneObject: false,
1875+
response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash,
1876+
ownerClientId,
1877+
parentNetworkId: null,
1878+
networkSceneHandle: null,
1879+
response.Position,
1880+
response.Rotation);
1881+
SpawnManager.SpawnNetworkObjectLocally(
1882+
networkObject,
1883+
SpawnManager.GetNetworkObjectId(),
1884+
sceneObject: false,
1885+
playerObject: true,
1886+
ownerClientId,
1887+
destroyWithScene: false);
18431888

18441889
ConnectedClients[ownerClientId].PlayerObject = networkObject;
18451890
}
@@ -1879,13 +1924,13 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint?
18791924
InvokeOnClientConnectedCallback(ownerClientId);
18801925
}
18811926

1882-
if (!createPlayerObject || (playerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
1927+
if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
18831928
{
18841929
return;
18851930
}
18861931

18871932
// Separating this into a contained function call for potential further future separation of when this notification is sent.
1888-
ApprovedPlayerSpawn(ownerClientId, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
1933+
ApprovedPlayerSpawn(ownerClientId, response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
18891934
}
18901935
else
18911936
{

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,22 @@ public void Handle(ref NetworkContext context)
101101
{
102102
// Note: Delegate creation allocates.
103103
// Note: ToArray() also allocates. :(
104-
networkManager.InvokeConnectionApproval(ConnectionData, senderId,
105-
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
104+
var response = networkManager.ConnectionApprovalCallback(
105+
new NetworkManager.ConnectionApprovalRequest
106106
{
107-
var localCreatePlayerObject = createPlayerObject;
108-
networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved, position, rotation);
107+
Payload = ConnectionData,
108+
ClientNetworkId = senderId
109109
});
110+
networkManager.HandleConnectionApproval(senderId, response);
110111
}
111112
else
112113
{
113-
networkManager.HandleApproval(senderId, networkManager.NetworkConfig.PlayerPrefab != null, null, true, null, null);
114+
var response = new NetworkManager.ConnectionApprovalResponse
115+
{
116+
Approved = true,
117+
CreatePlayerObject = networkManager.NetworkConfig.PlayerPrefab != null
118+
};
119+
networkManager.HandleConnectionApproval(senderId, response);
114120
}
115121
}
116122
}

com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public void Setup()
2424
[UnityTest]
2525
public IEnumerator ConnectionApproval()
2626
{
27-
NetworkManagerHelper.NetworkManagerObject.ConnectionApprovalCallback += NetworkManagerObject_ConnectionApprovalCallback;
27+
NetworkManagerHelper.NetworkManagerObject.ConnectionApprovalCallback = NetworkManagerObject_ConnectionApprovalCallback;
2828
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.ConnectionApproval = true;
2929
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.PlayerPrefab = null;
3030
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.ConnectionData = Encoding.UTF8.GetBytes(m_ValidationToken.ToString());
@@ -47,14 +47,22 @@ public IEnumerator ConnectionApproval()
4747
Assert.True(m_IsValidated);
4848
}
4949

50-
private void NetworkManagerObject_ConnectionApprovalCallback(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate callback)
50+
private NetworkManager.ConnectionApprovalResponse NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request)
5151
{
52-
var stringGuid = Encoding.UTF8.GetString(connectionData);
52+
var response = new NetworkManager.ConnectionApprovalResponse();
53+
var stringGuid = Encoding.UTF8.GetString(request.Payload);
5354
if (m_ValidationToken.ToString() == stringGuid)
5455
{
5556
m_IsValidated = true;
5657
}
57-
callback(false, null, m_IsValidated, null, null);
58+
59+
response.Approved = m_IsValidated;
60+
response.CreatePlayerObject = false;
61+
response.Position = null;
62+
response.Rotation = null;
63+
response.PlayerPrefabHash = null;
64+
65+
return response;
5866
}
5967

6068
[TearDown]

testproject/Assets/Tests/Manual/Scripts/ConnectionApprovalComponent.cs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ private void Start()
6565

6666
if (NetworkManager != null && NetworkManager.NetworkConfig.ConnectionApproval)
6767
{
68-
NetworkManager.ConnectionApprovalCallback += ConnectionApprovalCallback;
68+
NetworkManager.ConnectionApprovalCallback = ConnectionApprovalCallback;
6969

7070
if (m_ApprovalToken != string.Empty)
7171
{
@@ -151,41 +151,50 @@ public void OnDisconnectClient()
151151
/// <summary>
152152
/// Invoked only on the server, this will handle the various connection approval combinations
153153
/// </summary>
154-
/// <param name="dataToken">key or password to get approval</param>
155-
/// <param name="clientId">client identifier being approved</param>
156-
/// <param name="aprovalCallback">callback that should be invoked once it is determined if client is approved or not</param>
157-
private void ConnectionApprovalCallback(byte[] dataToken, ulong clientId, NetworkManager.ConnectionApprovedDelegate aprovalCallback)
154+
/// <param name="request">The connection approval request</param>
155+
/// <returns>ConnectionApprovalResult with the approval decision, with parameters</returns>
156+
private NetworkManager.ConnectionApprovalResponse ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request)
158157
{
159-
string approvalToken = Encoding.ASCII.GetString(dataToken);
158+
var response = new NetworkManager.ConnectionApprovalResponse();
159+
string approvalToken = Encoding.ASCII.GetString(request.Payload);
160160
var isTokenValid = approvalToken == m_ApprovalToken;
161-
if (m_SimulateFailure && m_SimulateFailure.isOn && IsServer && clientId != NetworkManager.LocalClientId)
161+
if (m_SimulateFailure && m_SimulateFailure.isOn && IsServer && request.ClientNetworkId != NetworkManager.LocalClientId)
162162
{
163163
isTokenValid = false;
164164
}
165165

166166
if (m_GlobalObjectIdHashOverride != 0 && m_PlayerPrefabOverride && m_PlayerPrefabOverride.isOn)
167167
{
168-
aprovalCallback.Invoke(true, m_GlobalObjectIdHashOverride, isTokenValid, null, null);
168+
response.Approved = isTokenValid;
169+
response.PlayerPrefabHash = m_GlobalObjectIdHashOverride;
170+
response.Position = null;
171+
response.Rotation = null;
172+
response.CreatePlayerObject = true;
169173
}
170174
else
171175
{
172-
aprovalCallback.Invoke(true, null, isTokenValid, null, null);
176+
response.Approved = isTokenValid;
177+
response.PlayerPrefabHash = null;
178+
response.Position = null;
179+
response.Rotation = null;
180+
response.CreatePlayerObject = true;
173181
}
174182

175-
176183
if (m_ConnectionMessageToDisplay)
177184
{
178185
if (isTokenValid)
179186
{
180-
AddNewMessage($"Client id {clientId} is authorized!");
187+
AddNewMessage($"Client id {request.ClientNetworkId} is authorized!");
181188
}
182189
else
183190
{
184-
AddNewMessage($"Client id {clientId} failed authorization!");
191+
AddNewMessage($"Client id {request.ClientNetworkId} failed authorization!");
185192
}
186193

187194
m_ConnectionMessageToDisplay.gameObject.SetActive(true);
188195
}
196+
197+
return response;
189198
}
190199

191200
/// <summary>

0 commit comments

Comments
 (0)