Skip to content

Commit c369bfd

Browse files
fix: distribute parented children distributing with root parent (#3203)
* fix This is the initial fix for distributing parented children that have the distributable and/or transferrable permissions set and have the same owner as the root parent, that has the distributable permission set, upon the owning client disconnecting when using a distributed authority network topology. * update Adding initial change log entry. * update adding PR number to entry * update Handle check for parent earlier. Add additional comment details. * fix Adding DAHost side object hierarchy redistribution when a client disconnects. Adding a DA specific work around for integration testing when instantiating and spawning a player within the test runner scene. * test The test to validate these changes using a DAHost. * style Removing white spaces * test Adding ownership locking to the test.
1 parent 21b7023 commit c369bfd

File tree

6 files changed

+355
-11
lines changed

6 files changed

+355
-11
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ Additional documentation and release notes are available at [Multiplayer Documen
1414

1515
- Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243)
1616
- Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219)
17-
- Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212)
17+
- Fixed issue with distributing parented children that have the distributable and/or transferrable permissions set and have the same owner as the root parent, that has the distributable permission set, were not being distributed to the same client upon the owning client disconnecting when using a distributed authority network topology. (#3203)
1818
- 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. (#3200)
1919
- Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3181)
2020

2121
### Changed
2222

23+
- Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212)
2324

2425
## [2.2.0] - 2024-12-12
2526

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -1225,7 +1225,7 @@ internal void OnClientDisconnectFromServer(ulong clientId)
12251225
}
12261226
NetworkManager.SpawnManager.ChangeOwnership(ownedObject, targetOwner, true);
12271227
// DANGO-TODO: Should we try handling inactive NetworkObjects?
1228-
// Ownership gets passed down to all children
1228+
// Ownership gets passed down to all children that have the same owner.
12291229
var childNetworkObjects = ownedObject.GetComponentsInChildren<NetworkObject>();
12301230
foreach (var childObject in childNetworkObjects)
12311231
{
@@ -1245,6 +1245,14 @@ internal void OnClientDisconnectFromServer(ulong clientId)
12451245
{
12461246
continue;
12471247
}
1248+
1249+
// If the child's owner is not the client disconnected and the objects are marked with either distributable or transferable, then
1250+
// do not change ownership.
1251+
if (childObject.OwnerClientId != clientId && (childObject.IsOwnershipDistributable || childObject.IsOwnershipTransferable))
1252+
{
1253+
continue;
1254+
}
1255+
12481256
NetworkManager.SpawnManager.ChangeOwnership(childObject, targetOwner, true);
12491257
if (EnableDistributeLogging)
12501258
{

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -1659,7 +1659,20 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla
16591659
}
16601660
if (NetworkManager.NetworkConfig.EnableSceneManagement)
16611661
{
1662-
NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle];
1662+
if (!NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(gameObject.scene.handle))
1663+
{
1664+
// Most likely this issue is due to an integration test
1665+
if (NetworkManager.LogLevel <= LogLevel.Developer)
1666+
{
1667+
NetworkLog.LogWarning($"Failed to find scene handle {gameObject.scene.handle} for {gameObject.name}!");
1668+
}
1669+
// Just use the existing handle
1670+
NetworkSceneHandle = gameObject.scene.handle;
1671+
}
1672+
else
1673+
{
1674+
NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle];
1675+
}
16631676
}
16641677
if (DontDestroyWithOwner && !IsOwnershipDistributable)
16651678
{

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

+37-8
Original file line numberDiff line numberDiff line change
@@ -1755,16 +1755,24 @@ internal void GetObjectDistribution(ref Dictionary<uint, Dictionary<ulong, List<
17551755
continue;
17561756
}
17571757

1758-
if (networkObject.IsOwnershipDistributable && !networkObject.IsOwnershipLocked)
1758+
// We first check for a parent and then if the parent is a NetworkObject
1759+
if (networkObject.transform.parent != null)
17591760
{
1760-
if (networkObject.transform.parent != null)
1761+
// If the parent is a NetworkObject, has the same owner, and this NetworkObject has the distributable =or= transferable permission
1762+
// then skip this child (NetworkObjects are always parented directly under other NetworkObjects)
1763+
// (later we determine if all children with the same owner will be transferred together as a group)
1764+
var parentNetworkObject = networkObject.transform.parent.GetComponent<NetworkObject>();
1765+
if (parentNetworkObject != null && parentNetworkObject.OwnerClientId == networkObject.OwnerClientId
1766+
&& (networkObject.IsOwnershipDistributable || networkObject.IsOwnershipTransferable))
17611767
{
1762-
var parentNetworkObject = networkObject.transform.parent.GetComponent<NetworkObject>();
1763-
if (parentNetworkObject != null && parentNetworkObject.OwnerClientId == networkObject.OwnerClientId)
1764-
{
1765-
continue;
1766-
}
1768+
continue;
17671769
}
1770+
}
1771+
1772+
// At this point we only allow things marked with the distributable permission and is not locked to be distributed
1773+
if (networkObject.IsOwnershipDistributable && !networkObject.IsOwnershipLocked)
1774+
{
1775+
17681776
// We have to check if it is an in-scene placed NetworkObject and if it is get the source prefab asset GlobalObjectIdHash value of the in-scene placed instance
17691777
// since all in-scene placed instances use unique GlobalObjectIdHash values.
17701778
var globalOjectIdHash = networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value ? networkObject.InScenePlacedSourceGlobalObjectIdHash : networkObject.GlobalObjectIdHash;
@@ -1879,8 +1887,29 @@ internal void DistributeNetworkObjects(ulong clientId)
18791887
{
18801888
if ((i % offsetCount) == 0)
18811889
{
1890+
var children = ownerList.Value[i].GetComponentsInChildren<NetworkObject>();
1891+
// Since the ownerList.Value[i] has to be distributable, then transfer all child NetworkObjects
1892+
// with the same owner clientId and are marked as distributable also to the same client to keep
1893+
// the owned distributable parent with the owned distributable children
1894+
foreach (var child in children)
1895+
{
1896+
// Ignore the parent and any child that does not have the same owner or that is already owned by the currently targeted client
1897+
if (child == ownerList.Value[i] || child.OwnerClientId != ownerList.Value[i].OwnerClientId || child.OwnerClientId == clientId)
1898+
{
1899+
continue;
1900+
}
1901+
if ((!child.IsOwnershipDistributable || !child.IsOwnershipTransferable) && NetworkManager.LogLevel == LogLevel.Developer)
1902+
{
1903+
NetworkLog.LogWarning($"Sibling {child.name} of root parent {ownerList.Value[i].name} is neither transferrable or distributable! Object distribution skipped and could lead to a potentially un-owned or owner-mismatched {nameof(NetworkObject)}!");
1904+
continue;
1905+
}
1906+
// Transfer ownership of all distributable =or= transferrable children with the same owner to the same client to preserve the sibling ownership tree.
1907+
ChangeOwnership(child, clientId, true);
1908+
// Note: We don't increment the distributed count for these children as they are skipped when getting the object distribution
1909+
}
1910+
// Finally, transfer ownership of the root parent
18821911
ChangeOwnership(ownerList.Value[i], clientId, true);
1883-
//if (EnableDistributeLogging)
1912+
if (EnableDistributeLogging)
18841913
{
18851914
Debug.Log($"[Client-{ownerList.Key}][NetworkObjectId-{ownerList.Value[i].NetworkObjectId} Distributed to Client-{clientId}");
18861915
}

0 commit comments

Comments
 (0)