Skip to content

fix: In-scene NetworkObjects disabled but never re-enabled after scene transition #1354

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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e1b9a02
fix
NoelStephensUnity Oct 21, 2021
34f44ff
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Oct 21, 2021
4081559
test
NoelStephensUnity Oct 25, 2021
bef8dbc
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Oct 25, 2021
7240475
style
NoelStephensUnity Oct 25, 2021
b7f94e4
style
NoelStephensUnity Oct 25, 2021
45716b6
style
NoelStephensUnity Oct 25, 2021
02f30b5
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Oct 26, 2021
1783927
style
NoelStephensUnity Oct 26, 2021
adc0377
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
andrews-unity Oct 28, 2021
5012691
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Nov 1, 2021
87a948b
fix and test
NoelStephensUnity Nov 1, 2021
ec589f4
test update
NoelStephensUnity Nov 1, 2021
1100c12
style
NoelStephensUnity Nov 1, 2021
1115632
updated changelog
NoelStephensUnity Nov 2, 2021
5d6e7da
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Nov 3, 2021
9039933
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Nov 8, 2021
be5884c
refactor
NoelStephensUnity Nov 8, 2021
4869760
updated changelog
NoelStephensUnity Nov 8, 2021
def0ad0
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Nov 8, 2021
d59f3da
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Nov 8, 2021
c55dc8f
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Nov 9, 2021
8a60e2e
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
NoelStephensUnity Nov 10, 2021
1e126b6
Merge develop into fix/InScene-NetworkObjects-DDOL-Disabled-MTT-1479
github-actions[bot] Nov 10, 2021
448358f
Merge branch 'develop' into fix/InScene-NetworkObjects-DDOL-Disabled-…
andrews-unity Nov 10, 2021
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
3 changes: 2 additions & 1 deletion com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager.
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1830,21 +1830,18 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader)
/// Moves all NetworkObjects that don't have the <see cref="NetworkObject.DestroyWithScene"/> set to
/// the "Do not destroy on load" scene.
/// </summary>
private void MoveObjectsToDontDestroyOnLoad()
internal void MoveObjectsToDontDestroyOnLoad()
Copy link
Contributor

Choose a reason for hiding this comment

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

It appears that these are being made internal so that you can test them directly? This is a testing anti-pattern. To paraphrase a great number of testing authorities: "How do you test internals? Through the public API."

Copy link
Collaborator Author

@NoelStephensUnity NoelStephensUnity Oct 26, 2021

Choose a reason for hiding this comment

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

Yeah... the test to validate this fix was a little tricky.
The only place the two methods pertaining to this bug and fix (MoveObjectsToDontDestroyOnLoad and MoveObjectsFromDontDestroyOnLoadToScene ) are called is when there is a full scene transition taking place. Since we cannot do a full scene transition (i.e. it would unload the Test Runner's temporary scene and would break the rest of the tests) in a unit and/or integration test, the only way to write a test (currently) to verify the fix in this PR was to create the same circumstances and call the same methods...which required them to be accessible.
If you want, I could reduce the anti-pattern footprint by just testing the MoveObjectsFromDontDestroyOnLoadToScene method and not including the MoveObjectsToDontDestroyOnLoad in that process (i.e. just disable it in the unit test itself).

{
// Move ALL NetworkObjects to the temp scene
// Move ALL NetworkObjects marked to persist scene transitions into the DDOL scene
var objectsToKeep = new HashSet<NetworkObject>(m_NetworkManager.SpawnManager.SpawnedObjectsList);

foreach (var sobj in objectsToKeep)
{
if (!sobj.DestroyWithScene || (sobj.IsSceneObject != null && sobj.IsSceneObject.Value && sobj.gameObject.scene == DontDestroyOnLoadScene))
if (!sobj.DestroyWithScene || sobj.gameObject.scene == DontDestroyOnLoadScene)
{
// Only move objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null)
// Only move dynamically spawned network objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
{
UnityEngine.Object.DontDestroyOnLoad(sobj.gameObject);
// Since we are doing a scene transition, disable the GameObject until the next scene is loaded
sobj.gameObject.SetActive(false);
}
}
else if (m_NetworkManager.IsServer)
Expand Down Expand Up @@ -1907,24 +1904,22 @@ private void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePl
/// Moves all spawned NetworkObjects (from do not destroy on load) to the scene specified
/// </summary>
/// <param name="scene">scene to move the NetworkObjects to</param>
private void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
{
// Move ALL NetworkObjects to the temp scene
var objectsToKeep = m_NetworkManager.SpawnManager.SpawnedObjectsList;

foreach (var sobj in objectsToKeep)
{
if (sobj.gameObject.scene == DontDestroyOnLoadScene && (sobj.IsSceneObject == null || sobj.IsSceneObject.Value))
{
continue;
}

// Only move objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null)
// If it is in the DDOL then
if (sobj.gameObject.scene == DontDestroyOnLoadScene)
{
// set it back to active at this point
sobj.gameObject.SetActive(true);
SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
// only move dynamically spawned network objects, with no parent as child objects will follow,
// back into the currently active scene
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
{
SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
}
}
}
}
Expand Down
125 changes: 124 additions & 1 deletion testproject/Assets/Tests/Runtime/NetworkSceneManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using UnityEngine.TestTools;
using Unity.Netcode.RuntimeTests;
using Unity.Netcode;
using Object = UnityEngine.Object;

namespace TestProject.RuntimeTests
{
Expand Down Expand Up @@ -980,7 +981,129 @@ public IEnumerator FastReaderAllocationTest()
nativeArray.Dispose();
fastBufferWriter.Dispose();
networkManager.Shutdown();
UnityEngine.Object.Destroy(networkManagerGameObject);
Object.Destroy(networkManagerGameObject);
}
}

public class NetworkSceneManagerDDOLTests
{
private NetworkManager m_ServerNetworkManager;
private GameObject m_NetworkManagerGameObject;
private GameObject m_DDOL_ObjectToSpawn;

protected float m_ConditionMetFrequency = 0.1f;

[UnitySetUp]
protected IEnumerator SetUp()
{
m_NetworkManagerGameObject = new GameObject("NetworkManager - Host");
m_ServerNetworkManager = m_NetworkManagerGameObject.AddComponent<NetworkManager>();

m_DDOL_ObjectToSpawn = new GameObject();
m_DDOL_ObjectToSpawn.AddComponent<NetworkObject>();
m_DDOL_ObjectToSpawn.AddComponent<DDOLBehaviour>();

m_ServerNetworkManager.NetworkConfig = new NetworkConfig()
{
ConnectionApproval = false,
NetworkPrefabs = new List<NetworkPrefab>(),
NetworkTransport = m_NetworkManagerGameObject.AddComponent<SIPTransport>(),
};
m_ServerNetworkManager.StartHost();
yield break;
}

[UnityTearDown]
protected IEnumerator TearDown()
{
m_ServerNetworkManager.Shutdown();

Object.Destroy(m_NetworkManagerGameObject);
Object.Destroy(m_DDOL_ObjectToSpawn);

yield break;
}

public enum DefaultState
{
IsEnabled,
IsDisabled
}

public enum MovedIntoDDOLBy
{
User,
NetworkSceneManager
}

public enum NetworkObjectType
{
InScenePlaced,
DynamicallySpawned
}

/// <summary>
/// Tests to make sure NetworkObjects moved into the DDOL will
/// restore back to their currently active state when a full
/// scene transition is complete.
/// This tests both in-scene placed and dynamically spawned NetworkObjects
[UnityTest]
public IEnumerator InSceneNetworkObjectState([Values(DefaultState.IsEnabled, DefaultState.IsDisabled)] DefaultState activeState,
[Values(MovedIntoDDOLBy.User, MovedIntoDDOLBy.NetworkSceneManager)] MovedIntoDDOLBy movedIntoDDOLBy,
[Values(NetworkObjectType.InScenePlaced, NetworkObjectType.DynamicallySpawned)] NetworkObjectType networkObjectType)
{
var isActive = activeState == DefaultState.IsEnabled ? true : false;
var isInScene = networkObjectType == NetworkObjectType.InScenePlaced ? true : false;
var networkObject = m_DDOL_ObjectToSpawn.GetComponent<NetworkObject>();
var ddolBehaviour = m_DDOL_ObjectToSpawn.GetComponent<DDOLBehaviour>();

// All tests require this to be false
networkObject.DestroyWithScene = false;

if (movedIntoDDOLBy == MovedIntoDDOLBy.User)
{
ddolBehaviour.MoveToDDOL();
}

// Sets whether we are in-scene or dynamically spawned NetworkObject
ddolBehaviour.SetInScene(isInScene);

Assert.That(networkObject.IsSpawned);

m_DDOL_ObjectToSpawn.SetActive(isActive);

m_ServerNetworkManager.SceneManager.MoveObjectsToDontDestroyOnLoad();

yield return new WaitForSeconds(0.03f);

// It should be isActive when MoveObjectsToDontDestroyOnLoad is called.
Assert.That(networkObject.isActiveAndEnabled == isActive);

m_ServerNetworkManager.SceneManager.MoveObjectsFromDontDestroyOnLoadToScene(SceneManager.GetActiveScene());

yield return new WaitForSeconds(0.03f);

// It should be isActive when MoveObjectsFromDontDestroyOnLoadToScene is called.
Assert.That(networkObject.isActiveAndEnabled == isActive);

//Done
networkObject.Despawn(false);
}

public class DDOLBehaviour : NetworkBehaviour
{
public void MoveToDDOL()
{
DontDestroyOnLoad(gameObject);
}

public void SetInScene(bool isInScene)
{
var networkObject = GetComponent<NetworkObject>();
networkObject.IsSceneObject = isInScene;
}
}

}

}