Skip to content

fix: Adding exception for silent failure for clients getting other player's object #844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
6 changes: 6 additions & 0 deletions com.unity.multiplayer.mlapi/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,12 @@ public void StopServer()
var disconnectedIds = new HashSet<ulong>();
//Don't know if I have to disconnect the clients. I'm assuming the NetworkTransport does all the cleaning on shtudown. But this way the clients get a disconnect message from server (so long it does't get lost)

// make sure all RPCs are flushed before transport disconnect clients
if (RpcQueueContainer != null)
{
RpcQueueContainer.ProcessAndFlushRpcQueue(queueType: RpcQueueContainer.RpcQueueProcessingTypes.Send, NetworkUpdateStage.PostLateUpdate); // flushing messages in case transport's disconnect
}

foreach (KeyValuePair<ulong, NetworkClient> pair in ConnectedClients)
{
if (!disconnectedIds.Contains(pair.Key))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,15 @@ public NetworkObject GetLocalPlayerObject()
}

/// <summary>
/// Returns the player object with a given clientId or null if one does not exist
/// Returns the player object with a given clientId or null if one does not exist. This is only valid server side.
/// </summary>
/// <returns>The player object with a given clientId or null if one does not exist</returns>
public NetworkObject GetPlayerNetworkObject(ulong clientId)
{
if (!NetworkManager.IsServer && NetworkManager.LocalClientId != clientId)
{
throw new NotServerException("Only the server can find player objects from other clients.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unit tests by a chance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

}
if (NetworkManager.ConnectedClients.TryGetValue(clientId, out NetworkClient networkClient))
{
return networkClient.PlayerObject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@ public static class MultiInstanceHelpers
/// <param name="clients">The clients NetworkManagers</param>
public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients)
{
clients = new NetworkManager[clientCount];
NetworkManagerInstances = new List<NetworkManager>();

CreateNewClients(clientCount, out clients);

for (int i = 0; i < clientCount; i++)
{
// Create gameObject
var go = new GameObject("NetworkManager - Client - " + i);
var go = new GameObject("NetworkManager - Server");

// Create networkManager component
clients[i] = go.AddComponent<NetworkManager>();
server = go.AddComponent<NetworkManager>();
NetworkManagerInstances.Insert(0, server);

// Set the NetworkConfig
clients[i].NetworkConfig = new NetworkConfig()
server.NetworkConfig = new NetworkConfig()
{
// Set the current scene to prevent unexpected log messages which would trigger a failure
RegisteredScenes = new List<string>() { SceneManager.GetActiveScene().name },
Expand All @@ -45,18 +48,28 @@ public static bool Create(int clientCount, out NetworkManager server, out Networ
};
}

NetworkManagerInstances = new List<NetworkManager>(clients);
return true;
}

/// <summary>
/// Used to add a client to the already existing list of clients
/// </summary>
/// <param name="clientCount">The amount of clients</param>
/// <param name="clients"></param>
/// <returns></returns>
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients)
{
clients = new NetworkManager[clientCount];

for (int i = 0; i < clientCount; i++)
{
// Create gameObject
var go = new GameObject("NetworkManager - Server");

var go = new GameObject("NetworkManager - Client - " + i);
// Create networkManager component
server = go.AddComponent<NetworkManager>();
NetworkManagerInstances.Insert(0, server);
clients[i] = go.AddComponent<NetworkManager>();

// Set the NetworkConfig
server.NetworkConfig = new NetworkConfig()
clients[i].NetworkConfig = new NetworkConfig()
{
// Set the current scene to prevent unexpected log messages which would trigger a failure
RegisteredScenes = new List<string>() { SceneManager.GetActiveScene().name },
Expand All @@ -65,9 +78,21 @@ public static bool Create(int clientCount, out NetworkManager server, out Networ
};
}

NetworkManagerInstances.AddRange(clients);
return true;
}

/// <summary>
/// Stops one single client and makes sure to cleanup any static variables in this helper
/// </summary>
/// <param name="clientToStop"></param>
public static void StopOneClient(NetworkManager clientToStop)
{
clientToStop.StopClient();
Object.Destroy(clientToStop.gameObject);
NetworkManagerInstances.Remove(clientToStop);
}

/// <summary>
/// Should always be invoked when finished with a single unit test
/// (i.e. during TearDown)
Expand Down Expand Up @@ -112,7 +137,7 @@ public static bool Start(bool host, NetworkManager server, NetworkManager[] clie
}
else
{
server.StartClient();
server.StartServer();
}

for (int i = 0; i < clients.Length; i++)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using System;
using System.Collections;
using MLAPI.Exceptions;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Object = System.Object;

namespace MLAPI.RuntimeTests
{
public class NetworkSpawnManagerTests
{
private NetworkManager m_ServerNetworkManager;
private NetworkManager[] m_ClientNetworkManagers;
private GameObject m_PlayerPrefab;
private int m_OriginalTargetFrameRate;

private ulong serverSideClientId => m_ServerNetworkManager.ServerClientId;
private ulong clientSideClientId => m_ClientNetworkManagers[0].LocalClientId;
private ulong otherClientSideClientId => m_ClientNetworkManagers[1].LocalClientId;

[UnitySetUp]
public IEnumerator Setup()
{
// Just always track the current target frame rate (will be re-applied upon TearDown)
m_OriginalTargetFrameRate = Application.targetFrameRate;

// Since we use frame count as a metric, we need to assure it runs at a "common update rate"
// between platforms (i.e. Ubuntu seems to run at much higher FPS when set to -1)
if (Application.targetFrameRate < 0 || Application.targetFrameRate > 120)
{
Application.targetFrameRate = 120;
}

// Create multiple NetworkManager instances
if (!MultiInstanceHelpers.Create(2, out NetworkManager server, out NetworkManager[] clients))
{
Debug.LogError("Failed to create instances");
Assert.Fail("Failed to create instances");
}

m_ServerNetworkManager = server;
m_ClientNetworkManagers = clients;

// Create playerPrefab
m_PlayerPrefab = new GameObject("Player");
NetworkObject networkObject = m_PlayerPrefab.AddComponent<NetworkObject>();

// Make it a prefab
MultiInstanceHelpers.MakeNetworkedObjectTestPrefab(networkObject);

// Set the player prefab
server.NetworkConfig.PlayerPrefab = m_PlayerPrefab;

for (int i = 0; i < clients.Length; i++)
{
clients[i].NetworkConfig.PlayerPrefab = m_PlayerPrefab;
}

// Start the instances
if (!MultiInstanceHelpers.Start(true, server, clients))
{
Debug.LogError("Failed to start instances");
Assert.Fail("Failed to start instances");
}

// Wait for connection on client side
for (int i = 0; i < clients.Length; i++)
{
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnected(clients[i]));
}

// Wait for connection on server side
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnectedToServer(server, clientCount: 3));
}

[Test]
public void TestServerCanAccessItsOwnPlayer()
{
// server can access its own player
var serverSideServerPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(serverSideClientId);
Assert.NotNull(serverSideServerPlayerObject);
Assert.AreEqual(serverSideClientId, serverSideServerPlayerObject.OwnerClientId);
}

[Test]
public void TestServerCanAccessOtherPlayers()
{
// server can access other players
var serverSideClientPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(clientSideClientId);
Assert.NotNull(serverSideClientPlayerObject);
Assert.AreEqual(clientSideClientId, serverSideClientPlayerObject.OwnerClientId);

var serverSideOtherClientPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(otherClientSideClientId);
Assert.NotNull(serverSideOtherClientPlayerObject);
Assert.AreEqual(otherClientSideClientId, serverSideOtherClientPlayerObject.OwnerClientId);
}

[Test]
public void TestClientCantAccessServerPlayer()
{
// client can't access server player
Assert.Throws<NotServerException>(() =>
{
m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(serverSideClientId);
});
}

[Test]
public void TestClientCanAccessOwnPlayer()
{
// client can access own player
var clientSideClientPlayerObject = m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(clientSideClientId);
Assert.NotNull(clientSideClientPlayerObject);
Assert.AreEqual(clientSideClientId, clientSideClientPlayerObject.OwnerClientId);
}

[Test]
public void TestClientCantAccessOtherPlayer()
{
// client can't access other player
Assert.Throws<NotServerException>(() =>
{
m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(otherClientSideClientId);
});
}

[Test]
public void TestServerGetsNullValueIfInvalidId()
{
// server gets null value if invalid id
var nullPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(9999);
Assert.Null(nullPlayer);
}

[Test]
public void TestServerCanUseGetLocalPlayerObject()
{
// test server can use GetLocalPlayerObject
var serverSideServerPlayerObject = m_ServerNetworkManager.SpawnManager.GetLocalPlayerObject();
Assert.NotNull(serverSideServerPlayerObject);
Assert.AreEqual(serverSideClientId, serverSideServerPlayerObject.OwnerClientId);
}

[Test]
public void TestClientCanUseGetLocalPlayerObject()
{
// test client can use GetLocalPlayerObject
var clientSideClientPlayerObject = m_ClientNetworkManagers[0].SpawnManager.GetLocalPlayerObject();
Assert.NotNull(clientSideClientPlayerObject);
Assert.AreEqual(clientSideClientId, clientSideClientPlayerObject.OwnerClientId);
}

[UnityTest]
public IEnumerator TestConnectAndDisconnect()
{
// test when client connects, player object is now available

// connect new client
if (!MultiInstanceHelpers.CreateNewClients(1, out NetworkManager[] clients))
{
Debug.LogError("Failed to create instances");
Assert.Fail("Failed to create instances");
}
var newClientNetworkManager = clients[0];
newClientNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
newClientNetworkManager.StartClient();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnected(newClientNetworkManager));
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => m_ServerNetworkManager.ConnectedClients.ContainsKey(newClientNetworkManager.LocalClientId)));
var newClientLocalClientId = newClientNetworkManager.LocalClientId;

// test new client can get that itself locally
var newPlayerObject = newClientNetworkManager.SpawnManager.GetLocalPlayerObject();
Assert.NotNull(newPlayerObject);
Assert.AreEqual(newClientLocalClientId, newPlayerObject.OwnerClientId);
// test server can get that new client locally
var serverSideNewClientPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(newClientLocalClientId);
Assert.NotNull(serverSideNewClientPlayer);
Assert.AreEqual(newClientLocalClientId, serverSideNewClientPlayer.OwnerClientId);

// test when client disconnects, player object no longer available.
var nbConnectedClients = m_ServerNetworkManager.ConnectedClients.Count;
MultiInstanceHelpers.StopOneClient(newClientNetworkManager);
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => m_ServerNetworkManager.ConnectedClients.Count == nbConnectedClients - 1));
serverSideNewClientPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(newClientLocalClientId);
Assert.Null(serverSideNewClientPlayer);
}

[UnityTearDown]
public IEnumerator Teardown()
{
// Shutdown and clean up both of our NetworkManager instances
MultiInstanceHelpers.Destroy();
UnityEngine.Object.Destroy(m_PlayerPrefab);

// Set the application's target frame rate back to its original value
Application.targetFrameRate = m_OriginalTargetFrameRate;
yield return new WaitForSeconds(0); // wait for next frame so everything is destroyed, so following tests can execute from clean environment
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading