Skip to content

Commit e02c3e7

Browse files
authored
fix: Various bugs causing rust test failures on trunk (#3941)
* Ensure objects are cleaned up on a failed spawn * Ensure messages are deferred until after the client is fully connected and synchronized * Fix SceneLoadingAndNotificationsTests * Cleanup unneeded changes * Fix up the logging on AuthorityLocalSpawn * Move tests back to trunk * fix style issues * standards fix * Small fixes * Fix null reference exception * dotnet-fix * dotnet-fix * Non-authority gets synchronize event now * Update object destroying on resynchronize * Update CHANGELOG * Fix NetworkObjectId check * Use internal RemoveParent instead of public one with extra checks
1 parent 61af4e6 commit e02c3e7

File tree

14 files changed

+412
-259
lines changed

14 files changed

+412
-259
lines changed

.yamato/_triggers.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,20 @@ pr_code_changes_checks:
7575
# Run API validation to early-detect all new APIs that would force us to release new minor version of the package. Note that for this to work the package version in package.json must correspond to "actual package state" which means that it should be higher than last released version
7676
- .yamato/vetting-test.yml#vetting_test
7777

78-
# Run package EditMode and Playmode package tests on 6000.5 and an older supported editor (6000.0)
79-
- .yamato/package-tests.yml#package_test_-_ngo_6000.5_mac
78+
# Run package EditMode and Playmode package tests on trunk and an older supported editor (6000.0)
79+
- .yamato/package-tests.yml#package_test_-_ngo_trunk_mac
8080
- .yamato/package-tests.yml#package_test_-_ngo_6000.0_win
8181

82-
# Run testproject EditMode and Playmode project tests on 6000.5 and an older supported editor (6000.0)
83-
- .yamato/project-tests.yml#test_testproject_win_6000.5
82+
# Run testproject EditMode and Playmode project tests on trunk and an older supported editor (6000.0)
83+
- .yamato/project-tests.yml#test_testproject_win_trunk
8484
- .yamato/project-tests.yml#test_testproject_mac_6000.0
8585

8686
# Run standalone test. We run it only on Ubuntu since it's the fastest machine, and it was noted that for example distribution on macOS is taking 40m since we switched to Apple Silicon
8787
# Coverage on other standalone machines is present in Nightly job so it's enough to not run all of them for PRs
8888
# desktop_standalone_test and cmb_service_standalone_test are both reusing desktop_standalone_build dependency so we run those in the same configuration on PRs to reduce waiting time.
89-
# Note that our daily tests will anyway run both test configurations in "minimal supported" and "6000.5" configurations
90-
- .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_ubuntu_il2cpp_6000.5
91-
- .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_testproject_ubuntu_il2cpp_6000.5
89+
# Note that our daily tests will anyway run both test configurations in "minimal supported" and "trunk" configurations
90+
- .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_ubuntu_il2cpp_trunk
91+
- .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_testproject_ubuntu_il2cpp_trunk
9292
triggers:
9393
expression: |-
9494
(pull_request.comment eq "ngo" OR

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1313

1414
### Changed
1515

16+
- Hardened error handling and recovery during `NetworkObject` spawn. (#3941)
1617
- Replaced Debug usage by NetcodeLog on `NetworkSpawnManager` and `NetworkObject`. (#3933)
1718
- Improved performance of `NetworkBehaviour`. (#3915)
1819
- Improved performance of `NetworkTransform`. (#3907)

com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -972,27 +972,39 @@ internal void HandleConnectionApproval(ulong ownerClientId, bool createPlayerObj
972972
}
973973

974974
// Server-side spawning (only if there is a prefab hash or player prefab provided)
975-
if (!NetworkManager.DistributedAuthorityMode && createPlayerObject && (playerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null))
975+
var idHashToSpawn = playerPrefabHash ?? NetworkManager.NetworkConfig.PlayerPrefab?.GetComponent<NetworkObject>()?.GlobalObjectIdHash;
976+
if (!NetworkManager.DistributedAuthorityMode && createPlayerObject && idHashToSpawn.HasValue)
976977
{
977-
var playerObject = playerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(playerPrefabHash.Value, ownerClientId, playerPosition, playerRotation)
978-
: NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, playerPosition, playerRotation);
978+
var playerObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(idHashToSpawn.Value, ownerClientId, playerPosition, playerRotation);
979979

980980
if (playerObject == null)
981981
{
982-
Debug.LogError($"[{nameof(NetworkObject)}] Player prefab is null! Cannot spawn player object!");
982+
if (NetworkManager.LogLevel <= LogLevel.Error)
983+
{
984+
NetworkLog.LogError($"[{nameof(NetworkObject)}] Player prefab is null! Cannot spawn player object!");
985+
}
983986
}
984987
else
985988
{
986989
// Spawn the player NetworkObject locally
987-
NetworkManager.SpawnManager.AuthorityLocalSpawn(
990+
if (NetworkManager.SpawnManager.AuthorityLocalSpawn(
988991
playerObject,
989992
NetworkManager.SpawnManager.GetNetworkObjectId(),
990993
sceneObject: false,
991994
playerObject: true,
992995
ownerClientId,
993-
destroyWithScene: false);
996+
destroyWithScene: false))
997+
{
998+
client.AssignPlayerObject(ref playerObject);
999+
}
1000+
else
1001+
{
1002+
if (NetworkManager.LogLevel <= LogLevel.Developer)
1003+
{
1004+
NetworkLog.LogError($"[{nameof(NetworkObject)}] Player prefab failed to spawn!");
1005+
}
1006+
}
9941007

995-
client.AssignPlayerObject(ref playerObject);
9961008
}
9971009
}
9981010

@@ -1122,8 +1134,25 @@ internal void CreateAndSpawnPlayer(ulong ownerId)
11221134
}
11231135
return;
11241136
}
1125-
var globalObjectIdHash = playerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
1126-
var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, playerPrefab.transform.position, playerPrefab.transform.rotation);
1137+
var prefabObject = playerPrefab.GetComponent<NetworkObject>();
1138+
if (prefabObject == null)
1139+
{
1140+
if (NetworkManager.LogLevel <= LogLevel.Normal)
1141+
{
1142+
NetworkLog.LogError("Failed to fetch valid player prefab. Ensure PlayerPrefab that is set in NetcodeConfig contains a NetworkObject component.");
1143+
}
1144+
return;
1145+
}
1146+
var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(prefabObject.GlobalObjectIdHash, ownerId, playerPrefab.transform.position, playerPrefab.transform.rotation);
1147+
if (networkObject == null)
1148+
{
1149+
if (NetworkManager.LogLevel <= LogLevel.Normal)
1150+
{
1151+
NetworkLog.LogError("Failed to spawn player prefab!");
1152+
}
1153+
return;
1154+
}
1155+
11271156
networkObject.IsSceneObject = false;
11281157
networkObject.NetworkManagerOwner = NetworkManager;
11291158
networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene);

com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,19 @@ internal static class FindObjects
2121
/// <typeparam name="T"></typeparam>
2222
/// <param name="includeInactive">When true, inactive objects will be included.</param>
2323
/// <param name="orderByIdentifier">When true, the array returned will be sorted by identifier.</param>
24-
/// <returns>Resulst as an <see cref="Array"/> of type T</returns>
24+
/// <returns>Results as an <see cref="Array"/> of type T</returns>
2525
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2626
public static T[] ByType<T>(bool includeInactive = false, bool orderByIdentifier = false) where T : Object
2727
{
2828
var inactive = includeInactive ? UnityEngine.FindObjectsInactive.Include : UnityEngine.FindObjectsInactive.Exclude;
2929
#if NGO_FINDOBJECTS_NOSORTING
3030
var results = Object.FindObjectsByType<T>(inactive);
31+
#if !NGO_FINDOBJECTS_UNORDERED_IDS
3132
if (orderByIdentifier)
3233
{
3334
Array.Sort(results, (a, b) => a.GetEntityId().CompareTo(b.GetEntityId()));
3435
}
36+
#endif
3537
#else
3638
var results = Object.FindObjectsByType<T>(inactive, orderByIdentifier ? UnityEngine.FindObjectsSortMode.InstanceID : UnityEngine.FindObjectsSortMode.None);
3739
#endif

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 35 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,15 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla
18091809
}
18101810
}
18111811

1812-
NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene);
1812+
if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene))
1813+
{
1814+
if (NetworkManagerOwner.LogLevel <= LogLevel.Normal)
1815+
{
1816+
NetworkLog.LogWarning($"[{name}] Failed to finish spawning!");
1817+
}
1818+
ResetOnDespawn();
1819+
return;
1820+
}
18131821

18141822
if ((NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.DAHost) || (!NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.IsServer))
18151823
{
@@ -1873,7 +1881,7 @@ public static NetworkObject InstantiateAndSpawn(GameObject networkPrefab, Networ
18731881
/// <summary>
18741882
/// This invokes <see cref="NetworkSpawnManager.InstantiateAndSpawn(NetworkObject, ulong, bool, bool, bool, Vector3, Quaternion)"/>.
18751883
/// </summary>
1876-
/// <param name="networkManager">The local instance of the NetworkManager connected to an session in progress.</param>
1884+
/// <param name="networkManager">The local instance of the NetworkManager connected to a session in progress.</param>
18771885
/// <param name="ownerClientId">The owner of the <see cref="NetworkObject"/> instance (defaults to server).</param>
18781886
/// <param name="destroyWithScene">Whether the <see cref="NetworkObject"/> instance will be destroyed when the scene it is located within is unloaded (default is false).</param>
18791887
/// <param name="isPlayerObject">Whether the <see cref="NetworkObject"/> instance is a player object or not (default is false).</param>
@@ -2185,6 +2193,11 @@ internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays)
21852193
m_CachedWorldPositionStays = worldPositionStays;
21862194
}
21872195

2196+
internal void ClearNetworkParenting()
2197+
{
2198+
m_LatestParent = null;
2199+
}
2200+
21882201
/// <summary>
21892202
/// Set the parent of the NetworkObject transform.
21902203
/// </summary>
@@ -3219,7 +3232,7 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
32193232
{
32203233
reader.ReadValueSafe(out ushort networkBehaviourId);
32213234
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
3222-
networkBehaviour.Synchronize(ref serializer, targetClientId);
3235+
networkBehaviour?.Synchronize(ref serializer, targetClientId);
32233236
}
32243237
}
32253238
}
@@ -3311,86 +3324,46 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject,
33113324
{
33123325
var endOfSynchronizationData = reader.Position + serializedObject.SynchronizationDataSize;
33133326

3314-
byte[] instantiationData = null;
3315-
if (serializedObject.HasInstantiationData)
3316-
{
3317-
reader.ReadValueSafe(out instantiationData);
3318-
}
3319-
3320-
// Attempt to create a local NetworkObject
3321-
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(serializedObject, instantiationData);
3322-
if (networkObject == null)
3327+
if (serializedObject.NetworkObjectId == default)
33233328
{
3324-
// Log the error that the NetworkObject failed to construct
3325-
if (networkManager.LogLevel <= LogLevel.Normal)
3326-
{
3327-
NetworkLog.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {serializedObject.Hash}.");
3328-
}
3329-
3330-
try
3331-
{
3332-
// If we failed to load this NetworkObject, then skip past the Network Variable and (if any) synchronization data
3333-
reader.Seek(endOfSynchronizationData);
3334-
}
3335-
catch (Exception ex)
3329+
if (networkManager.LogLevel <= LogLevel.Error)
33363330
{
3337-
Debug.LogException(ex);
3331+
NetworkLog.LogErrorServer($"[{nameof(GlobalObjectIdHash)}={serializedObject.Hash}] Received spawn request with invalid {nameof(NetworkObjectId)} {serializedObject.NetworkObjectId}. This should not happen!");
33383332
}
33393333

3340-
// We have nothing left to do here.
3334+
reader.Seek(endOfSynchronizationData);
33413335
return null;
33423336
}
3343-
networkObject.NetworkManagerOwner = networkManager;
33443337

3345-
// This will get set again when the NetworkObject is spawned locally, but we set it here ahead of spawning
3346-
// in order to be able to determine which NetworkVariables the client will be allowed to read.
3347-
networkObject.OwnerClientId = serializedObject.OwnerClientId;
3338+
// Do the SpawnManager parts of the object spawn
3339+
var succeeded = networkManager.SpawnManager.NonAuthorityLocalSpawn(in serializedObject, out var networkObject, reader, serializedObject.DestroyWithScene);
33483340

3349-
// Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours
3350-
networkObject.InvokeBehaviourNetworkPreSpawn();
3341+
// Process any deferred messages once the object is 100% finished spawning
3342+
// Ensure this is done whether the spawn succeeds or fails
3343+
networkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serializedObject.NetworkObjectId);
33513344

3352-
// Process the remaining synchronization data from the buffer
3353-
try
3345+
// Ensure that the buffer is completely reset
3346+
if (reader.Position != endOfSynchronizationData)
33543347
{
3355-
// Synchronize NetworkBehaviours
3356-
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
3357-
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
3358-
3359-
// Ensure that the buffer is completely reset
3360-
if (reader.Position != endOfSynchronizationData)
3348+
if (networkManager.LogLevel <= LogLevel.Normal)
33613349
{
3362-
Debug.LogWarning($"[Size mismatch] Expected: {endOfSynchronizationData} Currently At: {reader.Position}!");
3363-
reader.Seek(endOfSynchronizationData);
3350+
NetworkLog.LogWarning($"[{networkObject.name}][Deserialize][{nameof(NetworkBehaviour)}Synchronization][Size mismatch] Expected: {endOfSynchronizationData} Currently At: {reader.Position}!");
33643351
}
3365-
}
3366-
catch
3367-
{
33683352
reader.Seek(endOfSynchronizationData);
33693353
}
33703354

3371-
// If we are an in-scene placed NetworkObject and we originally had a parent but when synchronized we are
3372-
// being told we do not have a parent, then we want to clear the latest parent so it is not automatically
3373-
// "re-parented" to the original parent. This can happen if not unloading the scene and the parenting of
3374-
// the in-scene placed Networkobject changes several times over different sessions.
3375-
if (serializedObject.IsSceneObject && !serializedObject.HasParent && networkObject.m_LatestParent.HasValue)
3376-
{
3377-
networkObject.m_LatestParent = null;
3378-
}
3379-
3380-
// Spawn the NetworkObject
3381-
if (networkObject.IsSpawned)
3355+
// If the SpawnManager spawn doesn't succeed, be sure to clean up
3356+
if (!succeeded)
33823357
{
3383-
if (NetworkManager.Singleton.LogLevel <= LogLevel.Error)
3358+
// If the networkObject was created but the spawn failed, the created object needs to be destroyed
3359+
if (networkObject != null)
33843360
{
3385-
NetworkLog.LogErrorServer($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!");
3361+
Destroy(networkObject.gameObject);
33863362
}
3363+
33873364
return null;
33883365
}
33893366

3390-
// Invoke the non-authority local spawn method
3391-
// (It also invokes post spawn and handles processing derferred messages)
3392-
networkManager.SpawnManager.NonAuthorityLocalSpawn(networkObject, serializedObject, serializedObject.DestroyWithScene);
3393-
33943367
if (serializedObject.SyncObservers)
33953368
{
33963369
foreach (var observer in serializedObject.Observers)

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
112112
}
113113

114114
// Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy
115-
if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy))
115+
if (!networkManager.DistributedAuthorityMode || !networkManager.IsConnectedClient || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy))
116116
{
117117
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name);
118118
}

0 commit comments

Comments
 (0)