Skip to content

Commit cbe74c2

Browse files
authored
fix: Client throw exception when destroying spawned object (invalid operation) (#981)
1 parent 1311327 commit cbe74c2

File tree

3 files changed

+104
-0
lines changed

3 files changed

+104
-0
lines changed

com.unity.multiplayer.mlapi/Runtime/Core/NetworkObject.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,11 @@ public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientI
417417

418418
private void OnDestroy()
419419
{
420+
if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned)
421+
{
422+
throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
423+
}
424+
420425
if (NetworkManager != null && NetworkManager.SpawnManager != null && NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
421426
{
422427
NetworkManager.SpawnManager.OnDespawnObject(networkObject, false);
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System;
2+
using System.Collections;
3+
using MLAPI.Exceptions;
4+
using NUnit.Framework;
5+
using UnityEngine;
6+
using UnityEngine.TestTools;
7+
using Object = UnityEngine.Object;
8+
9+
namespace MLAPI.RuntimeTests
10+
{
11+
/// <summary>
12+
/// Tests calling destroy on spawned / unspawned <see cref="NetworkObject"/>s. Expected behavior:
13+
/// - Server or client destroy on unspawned => Object gets destroyed, no exceptions
14+
/// - Server destroy spawned => Object gets destroyed and despawned/destroyed on all clients. Server does not run <see cref="NetworkPrefaInstanceHandler.HandleNetworkPrefabDestroy"/>. Client runs it.
15+
/// - Client destroy spawned => throw exception.
16+
/// </summary>
17+
public class NetworkObjectDestroyTests : BaseMultiInstanceTest
18+
{
19+
protected override int NbClients => 1;
20+
21+
[UnitySetUp]
22+
public override IEnumerator Setup()
23+
{
24+
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab =>
25+
{
26+
// playerPrefab.AddComponent<TestDestroy>();
27+
});
28+
}
29+
30+
/// <summary>
31+
/// Tests that a server can destroy a NetworkObject and that it gets despawned correctly.
32+
/// </summary>
33+
/// <returns></returns>
34+
[UnityTest]
35+
public IEnumerator TestNetworkObjectServerDestroy()
36+
{
37+
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
38+
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
39+
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult));
40+
41+
// This is the *CLIENT VERSION* of the *CLIENT PLAYER*
42+
var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
43+
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
44+
45+
Assert.IsNotNull(serverClientPlayerResult.Result.gameObject);
46+
Assert.IsNotNull(clientClientPlayerResult.Result.gameObject);
47+
48+
// destroy the server player
49+
Object.Destroy(serverClientPlayerResult.Result.gameObject);
50+
51+
yield return null;
52+
53+
Assert.IsTrue(serverClientPlayerResult.Result == null); // Assert.IsNull doesn't work here
54+
55+
yield return null; // wait one frame more until we receive on client
56+
57+
Assert.IsTrue(clientClientPlayerResult.Result == null);
58+
59+
// create an unspawned networkobject and destroy it
60+
var go = new GameObject();
61+
go.AddComponent<NetworkObject>();
62+
Object.Destroy(go);
63+
64+
yield return null;
65+
Assert.IsTrue(go == null);
66+
}
67+
68+
/// <summary>
69+
/// Tests that a client cannot destroy a spawned networkobject.
70+
/// </summary>
71+
/// <returns></returns>
72+
[UnityTest]
73+
public IEnumerator TestNetworkObjectClientDestroy()
74+
{
75+
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
76+
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
77+
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult));
78+
79+
// This is the *CLIENT VERSION* of the *CLIENT PLAYER*
80+
var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
81+
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
82+
83+
// destroy the client player, this is not allowed
84+
LogAssert.Expect(LogType.Exception, "NotServerException: Destroy a spawned NetworkObject on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.");
85+
Object.DestroyImmediate(clientClientPlayerResult.Result.gameObject);
86+
}
87+
}
88+
}

com.unity.multiplayer.mlapi/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)