Skip to content

Commit

Permalink
fix: NetworkObject component should always precede NetworkBehaviour c…
Browse files Browse the repository at this point in the history
…omponents [MTT-7216] (#2685)

* fix

MTT-7216
This resolves the issue where an in-scene placed NetworkObject can have NetworkBehaviour components not invoke the OnNetworkDespawn method when doing a full scene transition (LoadSceneMode.Single) due to the way GameObjects destroy the attached components.

* test

This is a unit test to validate the re-ordering of the NetworkObject component.
  • Loading branch information
NoelStephensUnity authored Aug 30, 2023
1 parent a981703 commit f59f5e6
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 0 deletions.
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Fixed issue where a `NetworkBehaviour` component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the `NetworkBehaviour` component was positioned/ordered before the `NetworkObject` component. (#2685)
- Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682)
- Fixed issue where `NetworkAnimator` was not internally tracking changes to layer weights which prevented proper layer weight synchronization back to the original layer weight value. (#2674)
- Fixed "writing past the end of the buffer" error when calling ResetDirty() on managed network variables that are larger than 256 bytes when serialized. (#2670)
Expand Down
42 changes: 42 additions & 0 deletions com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,48 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje
}
}
}

if (networkObject != null)
{
OrderNetworkObject(networkObject);
}
}

// Assures the NetworkObject precedes any NetworkBehaviour on the same GameObject as the NetworkObject
private static void OrderNetworkObject(NetworkObject networkObject)
{
var monoBehaviours = networkObject.gameObject.GetComponents<MonoBehaviour>();
var networkObjectIndex = 0;
var firstNetworkBehaviourIndex = -1;
for (int i = 0; i < monoBehaviours.Length; i++)
{
if (monoBehaviours[i] == networkObject)
{
networkObjectIndex = i;
break;
}

var networkBehaviour = monoBehaviours[i] as NetworkBehaviour;
if (networkBehaviour != null)
{
// Get the index of the first NetworkBehaviour Component
if (firstNetworkBehaviourIndex == -1)
{
firstNetworkBehaviourIndex = i;
}
}
}

if (firstNetworkBehaviourIndex != -1 && networkObjectIndex > firstNetworkBehaviourIndex)
{
var positionsToMove = networkObjectIndex - firstNetworkBehaviourIndex;
for (int i = 0; i < positionsToMove; i++)
{
UnityEditorInternal.ComponentUtility.MoveComponentUp(networkObject);
}

EditorUtility.SetDirty(networkObject.gameObject);
}
}
}
}
87 changes: 87 additions & 0 deletions com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.RegularExpressions;
using NUnit.Framework;
using Unity.Netcode.Editor;
using UnityEngine;
using UnityEngine.TestTools;

Expand Down Expand Up @@ -64,9 +65,95 @@ public void GetBehaviourIndexOne()
Object.DestroyImmediate(gameObject);
}

/// <summary>
/// Verifies that a NetworkObject component that is positioned after a NetworkBehaviour component will
/// be migrated to a component index value that is before the lowest NetworkBehaviour component index value.
/// (The lowest NetworkBehaviour component's index value will also change when this happens)
/// </summary>
[Test]
public void NetworkObjectComponentOrder()
{
var gameObject = new GameObject(nameof(GetBehaviourIndexOne));
// Add the Networkbehaviour first
var networkBehaviour = gameObject.AddComponent<EmptyNetworkBehaviour>();
// Add an empty MonoBehaviour inbetween the NetworkBehaviour and NetworkObject
gameObject.AddComponent<EmptyMonoBehaviour>();
// Add the NetworkObject
var networkObject = gameObject.AddComponent<NetworkObject>();
var componentIndices = GetIndices(gameObject);

// Verify the NetworkObject procedes the NetworkBehaviour
Assert.True(componentIndices.NetworkObjectIndex > componentIndices.NetworkBehaviourIndex, $"[Initial Setup] NetworkObject index ({componentIndices.NetworkObjectIndex}) is not greater than the NetworkBehaviour index ({componentIndices.NetworkBehaviourIndex})!");

// Force-Invoke the CheckForNetworkObject method in order to verify the NetworkObject is moved
NetworkBehaviourEditor.CheckForNetworkObject(gameObject);
var adjustedIndices = GetIndices(gameObject);

Assert.True(ValidateComponentIndices(componentIndices, GetIndices(gameObject)), "NetworkObject did not get migrated below the NetworkBehaviour!");

// Cleanup
Object.DestroyImmediate(gameObject);
}

private bool ValidateComponentIndices(ComponentIndices previous, ComponentIndices current)
{
if (previous.NetworkObjectIndex != current.NetworkObjectIndex && previous.NetworkBehaviourIndex != current.NetworkBehaviourIndex)
{
if (current.NetworkObjectIndex < previous.NetworkObjectIndex && current.NetworkObjectIndex < current.NetworkBehaviourIndex)
{
return true;
}
}
return false;
}

private ComponentIndices GetIndices(GameObject gameObject)
{
// Get the index/order values for the added NetworkBehaviour and NetworkObject
var components = gameObject.GetComponents<MonoBehaviour>();
var componentIndices = new ComponentIndices()
{
NetworkObjectIndex = -1,
NetworkBehaviourIndex = -1
};
for (int i = 0; i < components.Length; i++)
{
if (componentIndices.NetworkObjectIndex != -1 && componentIndices.NetworkBehaviourIndex != -1)
{
break;
}
var component = components[i];
var networkObjectComponent = component as NetworkObject;
if (networkObjectComponent != null)
{
componentIndices.NetworkObjectIndex = i;
continue;
}
var networkBehaviourComponent = component as EmptyNetworkBehaviour;
if (networkBehaviourComponent != null)
{
componentIndices.NetworkBehaviourIndex = i;
continue;
}
}

return componentIndices;
}

private struct ComponentIndices
{
public int NetworkObjectIndex;
public int NetworkBehaviourIndex;
}

public class EmptyNetworkBehaviour : NetworkBehaviour
{

}

public class EmptyMonoBehaviour : MonoBehaviour
{

}
}
}

0 comments on commit f59f5e6

Please sign in to comment.