Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Fixed NetworkList to properly call INetworkSerializable's NetworkSerialize() method (#1682)
- Fixed The NetworkConfig's checksum hash includes the NetworkTick so that clients with a different tickrate than the server are identified and not allowed to connect (#1728)
- Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731)
- Fixed throwing an exception in OnNetworkUpdate causing other OnNetworkUpdate calls to not be executed. (#1739)
Comment thread
ShadauxCat marked this conversation as resolved.
Outdated

## [1.0.0-pre.5] - 2022-01-26

Expand Down
18 changes: 16 additions & 2 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,14 @@ internal void InvokeBehaviourNetworkSpawn()
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
ChildNetworkBehaviours[i].OnNetworkSpawn();
try
{
ChildNetworkBehaviours[i].OnNetworkSpawn();
Comment thread
ShadauxCat marked this conversation as resolved.
Outdated
}
catch (Exception e)
{
Console.WriteLine(e);
Comment thread
ShadauxCat marked this conversation as resolved.
Outdated
}
}
}

Expand All @@ -805,7 +812,14 @@ internal void InvokeBehaviourNetworkDespawn()
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
ChildNetworkBehaviours[i].InternalOnNetworkDespawn();
ChildNetworkBehaviours[i].OnNetworkDespawn();
try
{
ChildNetworkBehaviours[i].OnNetworkDespawn();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}

Expand Down
106 changes: 106 additions & 0 deletions testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.Collections;
using Unity.Netcode;
using Unity.Netcode.RuntimeTests;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.TestTools;

namespace TestProject.RuntimeTests
{

public class OnNetworkSpawnThrowsExceptionComponent : NetworkBehaviour
{
public static int NumClientSpawns = 0;
public override void OnNetworkSpawn()
{
if (IsClient)
{
++NumClientSpawns;
if (NumClientSpawns > 2)
{
throw new Exception("I'm misbehaving");
}
}
}
}
public class OnNetworkDespawnThrowsExceptionComponent : NetworkBehaviour
{
public static int NumClientDespawns = 0;
public override void OnNetworkDespawn()
{
if (IsClient)
{
++NumClientDespawns;
if (NumClientDespawns > 2)
{
throw new Exception("I'm misbehaving");
}
}
}

}
public class OnNetworkSpawnExceptionTests : BaseMultiInstanceTest
{
private GameObject m_Prefab;
private GameObject[] m_Objects = new GameObject[5];

[UnityTest]
public IEnumerator WhenOnNetworkSpawnThrowsException_FutureOnNetworkSpawnsAreNotPrevented()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These tests feel pretty rough in their current state.

  • There are lots of magic numbers. There are even magic numbers that depend on other magic numbers (2, 3, and 5)
  • You have 1 setup for 2 different tests. Is all the code in the setup necessary for both?
  • You have log asserts in your test setup. Are these asserts actual tests, or are they an attempt to verify that setup has done what it's expected to?
  • You are using the multi-instance tests and have a client but it's unclear if this is actually necessary for this test (don't these virtual methods get invoked on the server too? Can't you write a much simpler/more contained UnityTest that doesn't involve a client or waiting for frames with timeout asserts and if (IsClient)?)
  • It's unclear (to me) why you have if (NumClientSpawns > 2) guard in the test components.

While it's a very contentious topic, generally speaking, the more self-contained a test is, the better for understanding and lower the risk for future false positives. I'm not personally a fan of having to read 3 classes and 6 methods to understand what's actually happening in this 1 line test case.

Copy link
Copy Markdown
Member

@NoelStephensUnity NoelStephensUnity May 23, 2022

Choose a reason for hiding this comment

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

While throwing an exception in a component added to the player prefab is valid, I think this test should add more NetworkBehaviour derived components because the real test is to validate that all NetworkBehaviour components on a GameObject get OnNetworkSpawn and OnNetworkDespawn invoked even if one of the components throws and exception.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think I've addressed everything you mentioned here, except for the use of multi-instance tests. I think it's valuable to test that the behavior is the same on both server and client. I did, however, update the test to make sure it isn't exclusively a client-side test, since OnNetworkSpawn and OnNetworkDespawn are called on the server too.

{
//Spawning was done during setup
Assert.AreEqual(5, OnNetworkSpawnThrowsExceptionComponent.NumClientSpawns);
yield return null;
}

[UnityTest]
public IEnumerator WhenOnNetworkDespawnThrowsException_FutureOnNetworkDespawnsAreNotPrevented()
{
//Spawning was done during setup. Now we despawn.
for (var i = 0; i < 5; ++i)
{
m_Objects[i].GetComponent<NetworkObject>().Despawn();
}

var result = new MultiInstanceHelpers.CoroutineResultWrapper<bool>();
yield return MultiInstanceHelpers.Run(
MultiInstanceHelpers.WaitForCondition(
() => OnNetworkDespawnThrowsExceptionComponent.NumClientDespawns == 5, result));
Assert.IsTrue(result.Result);
}

public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, _ =>
{
m_Prefab = new GameObject();
var networkObject = m_Prefab.AddComponent<NetworkObject>();
m_Prefab.AddComponent<OnNetworkSpawnThrowsExceptionComponent>();
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject);

var validNetworkPrefab = new NetworkPrefab();
validNetworkPrefab.Prefab = m_Prefab;
m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(validNetworkPrefab);
foreach (var client in m_ClientNetworkManagers)
{
client.NetworkConfig.NetworkPrefabs.Add(validNetworkPrefab);
}
});

for (var i = 0; i < 5; ++i)
{
var obj = UnityEngine.Object.Instantiate(m_Prefab);
m_Objects[i] = obj;
obj.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
obj.GetComponent<NetworkObject>().Spawn();
}

var result = new MultiInstanceHelpers.CoroutineResultWrapper<bool>();
MultiInstanceHelpers.Run(
MultiInstanceHelpers.WaitForCondition(
() => OnNetworkSpawnThrowsExceptionComponent.NumClientSpawns == 5, result));
Assert.IsTrue(result.Result);
}
protected override int NbClients => 1;
}
}

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