Skip to content

fix: inscene networkobjects get destroyed upon client networkmanager shutting down without connecting #1809

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

2 changes: 1 addition & 1 deletion com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Removed `com.unity.modules.animation`, `com.unity.modules.physics` and `com.unity.modules.physics2d` dependencies from the package (#1812)

### Fixed

- Fixed in-scene NetworkObjects get destroyed if a client fails to connect and shuts down the NetworkManager. (#1809)
- Fixed user never being notified in the editor that a NetworkBehaviour requires a NetworkObject to function properly. (#1808)
- Fixed PlayerObjects and dynamically spawned NetworkObjects not being added to the NetworkClient's OwnedObjects (#1801)
- Fixed issue where NetworkManager would continue starting even if the NetworkTransport selected failed. (#1780)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ internal void DespawnAndDestroyNetworkObjects()

OnDespawnObject(networkObjects[i], shouldDestroy);
}
else
else if (networkObjects[i].IsSceneObject != null && !networkObjects[i].IsSceneObject.Value)
Copy link
Contributor

Choose a reason for hiding this comment

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

Worth a comment for clarity of

"IsSceneObject will be null if I've never connected, or will be true (and stay true) if I've connected and am (and always will be) a scene object."

{
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@

using System;
using System.Collections;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
using Unity.Netcode;
using Unity.Netcode.TestHelpers.Runtime;
using Object = UnityEngine.Object;

namespace TestProject.RuntimeTests
{
public class SceneObjectsNotDestroyedOnShutdownTest
{
private const string k_TestScene = "InSceneNetworkObject";
private const string k_SceneObjectName = "InSceneObject";
private Scene m_TestScene;
private NetworkManager m_ClientNetworkManager;
private GameObject m_NetworkManagerGameObject;
private WaitForSeconds m_DefaultWaitForTick = new WaitForSeconds(1.0f / 30);

[SetUp]
public void Setup()
{
m_NetworkManagerGameObject = new GameObject();
m_ClientNetworkManager = m_NetworkManagerGameObject.AddComponent<NetworkManager>();
m_ClientNetworkManager.NetworkConfig = new NetworkConfig();
m_ClientNetworkManager.NetworkConfig.NetworkTransport = m_NetworkManagerGameObject.AddComponent<BlankTestingTransport>();
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
SceneManager.LoadSceneAsync(k_TestScene, LoadSceneMode.Additive);
}

private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
if (scene.name == k_TestScene)
{
m_TestScene = scene;
}
}

[UnityTest]
public IEnumerator SceneObjectsNotDestroyedOnShutdown()
{
var timeoutHelper = new TimeoutHelper(2);

// Wait for the scene with the in-scene placed NetworkObject to be loaded.
yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => m_TestScene.IsValid() && m_TestScene.isLoaded, timeoutHelper);
Assert.False(timeoutHelper.TimedOut, "Timed out waiting for scene to load!");

var loadedInSceneObject = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.name == k_SceneObjectName).FirstOrDefault();

Assert.IsNotNull(loadedInSceneObject, $"Failed to find {k_SceneObjectName} before starting client!");

// Only start the client
m_ClientNetworkManager.StartClient();

// Wait for a tick
yield return m_DefaultWaitForTick;

// Shutdown the client
m_ClientNetworkManager.Shutdown();

// Wait for a tick
yield return m_DefaultWaitForTick;

// Find the same object
loadedInSceneObject = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.name == k_SceneObjectName).FirstOrDefault();

// Verify it still exists
Assert.IsNotNull(loadedInSceneObject, $"Failed to find {k_SceneObjectName} after starting client!");
}

[TearDown]
public void TearDown()
{
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;

if (m_TestScene.IsValid() && m_TestScene.isLoaded)
{
SceneManager.UnloadSceneAsync(m_TestScene);
}

if (m_NetworkManagerGameObject != null)
{
Object.DestroyImmediate(m_NetworkManagerGameObject);
}
}

internal class BlankTestingTransport : TestingNetworkTransport
{
public override ulong ServerClientId { get; } = 0;
public override void Send(ulong clientId, ArraySegment<byte> payload, NetworkDelivery networkDelivery)
{
}

public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime)
{
clientId = 0;
payload = new ArraySegment<byte>();
receiveTime = 0;
return NetworkEvent.Nothing;
}

public override bool StartClient()
{
return true;
}

public override bool StartServer()
{
return true;
}

public override void DisconnectRemoteClient(ulong clientId)
{
}

public override void DisconnectLocalClient()
{
}

public override ulong GetCurrentRtt(ulong clientId)
{
return 0;
}

public override void Shutdown()
{
}

public override void Initialize(NetworkManager networkManager = null)
{
}
}
}
}

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