Skip to content

Commit

Permalink
fix: client owned NetworkObject with prefabhandler destroy order inco…
Browse files Browse the repository at this point in the history
…rrect on host-server side (Backport) (#3202)

* fix

Pass in false for the destroy object parameter when cleaning up objects owned by a client upon the client disconnecting and the spawned objects have a prefab handler registered for their associated network prefab.
Cleaned up NetworkConnectionManager client disconnection a bit.

* update

migrating some NetworkSpawnManager updates from v2 to v1 and also fixing a few issues that were not back ported.

* test

Bringing the NetworkPrefabOverrideTests into v1.
Updating the NetcodeIntegrtionTestHelpers with some v2 improvements.

* update

adding changelog entry.

* style

continue vs nested if update.
  • Loading branch information
NoelStephensUnity authored Jan 10, 2025
1 parent bd738b3 commit eec1bdd
Show file tree
Hide file tree
Showing 6 changed files with 443 additions and 40 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 @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3202)
- Fixed issue where `NetworkRigidBody2D` was still using the deprecated `isKinematic` property in Unity versions 2022.3 and newer. (#3199)
- Fixed issue where an exception was thrown when calling `NetworkManager.Shutdown` after calling `UnityTransport.Shutdown`. (#3118)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,11 @@ internal void OnClientDisconnectFromServer(ulong clientId)
{
if (NetworkManager.PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash))
{
// If the player is spawned, then despawn before invoking HandleNetworkPrefabDestroy
if (playerObject.IsSpawned)
{
NetworkManager.SpawnManager.DespawnObject(ConnectedClients[clientId].PlayerObject, false);
}
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject);
}
else if (playerObject.IsSpawned)
Expand All @@ -984,40 +989,34 @@ internal void OnClientDisconnectFromServer(ulong clientId)

// Get the NetworkObjects owned by the disconnected client
var clientOwnedObjects = NetworkManager.SpawnManager.GetClientOwnedObjects(clientId);
if (clientOwnedObjects == null)

// Handle despawn & destroy or change ownership
for (int i = clientOwnedObjects.Count - 1; i >= 0; i--)
{
// This could happen if a client is never assigned a player object and is disconnected
// Only log this in verbose/developer mode
if (NetworkManager.LogLevel == LogLevel.Developer)
var ownedObject = clientOwnedObjects[i];
if (!ownedObject)
{
NetworkLog.LogWarning($"ClientID {clientId} disconnected with (0) zero owned objects! Was a player prefab not assigned?");
continue;
}
}
else
{
// Handle changing ownership and prefab handlers
for (int i = clientOwnedObjects.Count - 1; i >= 0; i--)
if (!ownedObject.DontDestroyWithOwner)
{
var ownedObject = clientOwnedObjects[i];
if (ownedObject != null)
if (NetworkManager.PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash))
{
if (!ownedObject.DontDestroyWithOwner)
if (ownedObject.IsSpawned)
{
if (NetworkManager.PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash))
{
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]);
}
else
{
Object.Destroy(ownedObject.gameObject);
}
}
else if (!NetworkManager.ShutdownInProgress)
{
ownedObject.RemoveOwnership();
NetworkManager.SpawnManager.DespawnObject(ownedObject, false);
}
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]);
}
else
{
Object.Destroy(ownedObject.gameObject);
}
}
else if (!NetworkManager.ShutdownInProgress)
{
ownedObject.RemoveOwnership();
}
}

// TODO: Could(should?) be replaced with more memory per client, by storing the visibility
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,10 +435,15 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ

var networkObject = networkPrefab;
// Host spawns the ovveride and server spawns the original prefab unless forceOverride is set to true where both server or host will spawn the override.
if (forceOverride || NetworkManager.IsHost)
if (forceOverride || NetworkManager.IsHost || NetworkManager.PrefabHandler.ContainsHandler(networkPrefab.GlobalObjectIdHash))
{
networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, position, rotation);
}
else // Under this case, server instantiate the prefab passed in.
{
networkObject = InstantiateNetworkPrefab(networkPrefab.gameObject, networkPrefab.GlobalObjectIdHash, position, rotation);
}

if (networkObject == null)
{
Debug.LogError($"Failed to instantiate and spawn {networkPrefab.name}!");
Expand All @@ -447,7 +452,15 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ
networkObject.IsPlayerObject = isPlayerObject;
networkObject.transform.position = position;
networkObject.transform.rotation = rotation;
networkObject.SpawnWithOwnership(ownerClientId, destroyWithScene);
// If spawning as a player, then invoke SpawnAsPlayerObject
if (isPlayerObject)
{
networkObject.SpawnAsPlayerObject(ownerClientId, destroyWithScene);
}
else // Otherwise just spawn with ownership
{
networkObject.SpawnWithOwnership(ownerClientId, destroyWithScene);
}
return networkObject;
}

Expand Down Expand Up @@ -512,17 +525,38 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow
}
else
{
// Create prefab instance
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
networkObject.transform.position = position ?? networkObject.transform.position;
networkObject.transform.rotation = rotation ?? networkObject.transform.rotation;
networkObject.NetworkManagerOwner = NetworkManager;
networkObject.PrefabGlobalObjectIdHash = globalObjectIdHash;
// Create prefab instance while applying any pre-assigned position and rotation values
networkObject = InstantiateNetworkPrefab(networkPrefabReference, globalObjectIdHash, position, rotation);
}
}
return networkObject;
}

/// <summary>
/// Instantiates a network prefab instance, assigns the base prefab <see cref="NetworkObject.GlobalObjectIdHash"/>, positions, and orients
/// the instance.
/// !!! Should only be invoked by <see cref="GetNetworkObjectToSpawn"/> unless used by an integration test !!!
/// </summary>
/// <remarks>
/// <param name="prefabGlobalObjectIdHash"> should be the base prefab <see cref="NetworkObject.GlobalObjectIdHash"/> value and not the
/// overrided value.
/// (Can be used for integration testing)
/// </remarks>
/// <param name="networkPrefab">prefab to instantiate</param>
/// <param name="prefabGlobalObjectIdHash"><see cref="NetworkObject.GlobalObjectIdHash"/> of the base prefab instance</param>
/// <param name="position">conditional position in place of the network prefab's default position</param>
/// <param name="rotation">conditional rotation in place of the network prefab's default rotation</param>
/// <returns>the instance of the <see cref="NetworkObject"/></returns>
internal NetworkObject InstantiateNetworkPrefab(GameObject networkPrefab, uint prefabGlobalObjectIdHash, Vector3? position, Quaternion? rotation)
{
var networkObject = UnityEngine.Object.Instantiate(networkPrefab).GetComponent<NetworkObject>();
networkObject.transform.position = position ?? networkObject.transform.position;
networkObject.transform.rotation = rotation ?? networkObject.transform.rotation;
networkObject.NetworkManagerOwner = NetworkManager;
networkObject.PrefabGlobalObjectIdHash = prefabGlobalObjectIdHash;
return networkObject;
}

/// <summary>
/// Creates a local NetowrkObject to be spawned.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,29 @@ public static void MakeNetworkObjectTestPrefab(NetworkObject networkObject, uint
}
}

/// <summary>
/// Creates a <see cref="NetworkObject"/> to be used with integration testing
/// </summary>
/// <param name="baseName">namr of the object</param>
/// <param name="owner">owner of the object</param>
/// <param name="moveToDDOL">when true, the instance is automatically migrated into the DDOL</param>
/// <returns></returns>
internal static GameObject CreateNetworkObject(string baseName, NetworkManager owner, bool moveToDDOL = false)
{
var gameObject = new GameObject
{
name = baseName
};
var networkObject = gameObject.AddComponent<NetworkObject>();
networkObject.NetworkManagerOwner = owner;
MakeNetworkObjectTestPrefab(networkObject);
if (moveToDDOL)
{
Object.DontDestroyOnLoad(gameObject);
}
return gameObject;
}

public static GameObject CreateNetworkObjectPrefab(string baseName, NetworkManager server, params NetworkManager[] clients)
{
void AddNetworkPrefab(NetworkConfig config, NetworkPrefab prefab)
Expand All @@ -538,13 +561,7 @@ void AddNetworkPrefab(NetworkConfig config, NetworkPrefab prefab)
Assert.IsNotNull(server, prefabCreateAssertError);
Assert.IsFalse(server.IsListening, prefabCreateAssertError);

var gameObject = new GameObject
{
name = baseName
};
var networkObject = gameObject.AddComponent<NetworkObject>();
networkObject.NetworkManagerOwner = server;
MakeNetworkObjectTestPrefab(networkObject);
var gameObject = CreateNetworkObject(baseName, server);
var networkPrefab = new NetworkPrefab() { Prefab = gameObject };

// We could refactor this test framework to share a NetworkPrefabList instance, but at this point it's
Expand Down
Loading

0 comments on commit eec1bdd

Please sign in to comment.