Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2e4f489
chore: started replacing exceptions
noellie-velez Jan 30, 2026
78b50c7
chore: use correct log level and type
noellie-velez Jan 30, 2026
667b22f
styling and cleanup
noellie-velez Jan 30, 2026
9a0b665
NotListeningException marked obsolete + fix test
noellie-velez Jan 30, 2026
3e0cffb
Merge branch 'develop-2.0.0' into chore/remove-spawn-state-exceptions…
noellie-velez Jan 30, 2026
90b5dd3
replace SceneObject by NetworkObject in log
noellie-velez Jan 30, 2026
b366562
Keep Exception on serialize out of buffer space
noellie-velez Feb 26, 2026
9abffa9
Merge branch 'develop-2.0.0' into chore/remove-spawn-state-exceptions…
noellie-velez Feb 26, 2026
cf5d039
Keeping exeption for deserialization buffer issue
noellie-velez Feb 26, 2026
9929552
Merge branch 'develop-2.0.0' into chore/remove-spawn-state-exceptions…
noellie-velez Mar 9, 2026
e7e3dad
Add duplicate show hide test
noellie-velez Mar 9, 2026
df715b3
Fix show behavior when duplicated show call
noellie-velez Mar 10, 2026
50bba6f
Changelog update
noellie-velez Mar 10, 2026
b259c3c
Update changelog
noellie-velez Mar 10, 2026
ac6ca30
remove changelog trailing spaces
noellie-velez Mar 10, 2026
cd12708
Merge develop-2.0.0 into chore/remove-spawn-state-exceptions-network-…
netcode-ci-service Mar 12, 2026
314c913
Merge branch 'develop-2.0.0' into chore/remove-spawn-state-exceptions…
EmandM Mar 12, 2026
1b42bc0
Merge branch 'develop-2.0.0' into chore/remove-spawn-state-exceptions…
noellie-velez Mar 16, 2026
06858a8
Fix wrong DAHost check on network hide and show
noellie-velez Mar 16, 2026
82dc3b0
Merge develop-2.0.0 into chore/remove-spawn-state-exceptions-network-…
netcode-ci-service Mar 17, 2026
ed35a91
Merge develop-2.0.0 into chore/remove-spawn-state-exceptions-network-…
netcode-ci-service Mar 17, 2026
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
5 changes: 3 additions & 2 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,19 @@ Additional documentation and release notes are available at [Multiplayer Documen


### Removed

- Removed un-needed exceptions on `NetworkObject.cs`. (#3867)

### Fixed

- Fixed issue where an attachable could log an error upon being de-spawned during shutdown. (#3895)
- NestedNetworkVariables initialized with no value no longer throw an error. (#3891)
- Fixed `NetworkShow` behavior when it is called twice. (#3867)

### Security


### Obsolete

- `NotListeningException` is now marked as obsolete as it is no longer used internally. (#3867)

## [2.10.0] - 2026-03-01

Expand Down
112 changes: 83 additions & 29 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,10 @@ internal NetworkSceneHandle GetSceneOriginHandle()
{
if (SceneOriginHandle.IsEmpty() && IsSpawned && IsSceneObject != false)
{
throw new Exception($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!");
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!");
}
}
return !SceneOriginHandle.IsEmpty() ? SceneOriginHandle : gameObject.scene.handle;
}
Expand All @@ -1481,32 +1484,40 @@ public void NetworkShow(ulong clientId)
{
if (!IsSpawned)
{
throw new SpawnStateException("Object is not spawned");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] Attempted NetworkShow while {nameof(NetworkObject)} not spawned.");
}
return;
}

if (!HasAuthority)
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only the owner-authority can change visibility when distributed authority mode is enabled!");
}
return;
}
else
{
throw new NotServerException("Only the authority can change visibility");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only the server can change visibility!");
}
return;
}
}

if (Observers.Contains(clientId))
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
Debug.LogError($"The object {name} is already visible to Client-{clientId}!");
return;
}
else
if (NetworkManagerOwner.LogLevel <= LogLevel.Developer)
{
throw new NotServerException("Only server can change visibility");
NetworkLog.LogWarning($"[{name}] {nameof(NetworkObject)} is already visible to Client-{clientId}! (ignoring)");
}
return;
}

if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId))
Expand Down Expand Up @@ -1556,8 +1567,8 @@ public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientI
/// <remarks>
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for a currently visible <see cref="NetworkObject"/>.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be de-spawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be de-spawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
Expand All @@ -1568,18 +1579,30 @@ public void NetworkHide(ulong clientId)
{
if (!IsSpawned)
{
throw new SpawnStateException("Object is not spawned");
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] Attempted NetworkHide while {nameof(NetworkObject)} is not spawned.");
}
return;
}

if (!HasAuthority && !NetworkManagerOwner.DAHost)
if (!HasAuthority)
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only the owner-authority can change visibility when distributed authority mode is enabled!");
}
return;
}
else
{
throw new NotServerException("Only the authority can change visibility");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only the server can change visibility!");
}
return;
}
}

Expand All @@ -1589,9 +1612,9 @@ public void NetworkHide(ulong clientId)
{
if (NetworkManagerOwner.LogLevel <= LogLevel.Developer)
{
Debug.LogWarning($"{name} is already hidden from Client-{clientId}! (ignoring)");
return;
NetworkLog.LogWarning($"[{name}] {nameof(NetworkObject)} already hidden from Client-{clientId}! (ignoring)");
}
return;
}
Observers.Remove(clientId);

Expand Down Expand Up @@ -1724,18 +1747,30 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla

if (!NetworkManagerOwner.IsListening)
{
throw new NotListeningException($"{nameof(NetworkManagerOwner)} is not listening, start a server or host before spawning objects");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] {nameof(NetworkManagerOwner)} is not listening, start a server or host before spawning objects.");
}
return;
}

if ((!NetworkManagerOwner.IsServer && !NetworkManagerOwner.DistributedAuthorityMode) || (NetworkManagerOwner.DistributedAuthorityMode && !NetworkManagerOwner.LocalClient.IsSessionOwner && NetworkManagerOwner.LocalClientId != ownerClientId))
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
throw new NotServerException($"When distributed authority mode is enabled, you can only spawn NetworkObjects that belong to the local instance! Local instance id {NetworkManagerOwner.LocalClientId} is not the same as the assigned owner id: {ownerClientId}!");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] When distributed authority mode is enabled, you can only spawn NetworkObjects that belong to the local instance! Local instance id {NetworkManagerOwner.LocalClientId} is not the same as the assigned owner id: {ownerClientId}!");
}
return;
}
else
{
throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only server can spawn {nameof(NetworkObject)}s.");
}
return;
}
}

Expand Down Expand Up @@ -2256,7 +2291,10 @@ private void OnTransformParentChanged()
return;
}
transform.parent = m_CachedParent;
Debug.LogException(new NotListeningException($"[{name}] {nameof(networkManager)} is not listening, start a server or host before re-parenting"));
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] {nameof(networkManager)} is not listening, start a server or host before re-parenting.");
}
return;
}

Expand All @@ -2273,7 +2311,10 @@ private void OnTransformParentChanged()
else
{
transform.parent = m_CachedParent;
Debug.LogException(new SpawnStateException($"[{name}] {nameof(NetworkObject)} can only be re-parented after being spawned"));
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] {nameof(NetworkObject)} can only be re-parented after being spawned!");
}
}
return;
}
Expand All @@ -2293,7 +2334,7 @@ private void OnTransformParentChanged()
}
else
{
Debug.LogException(new NotServerException($"[{name}] Only the server can re-parent {nameof(NetworkObject)}s"));
NetworkLog.LogError($"[{name}] Only the server can re-parent {nameof(NetworkObject)}s.");
}
}
return;
Expand All @@ -2307,14 +2348,20 @@ private void OnTransformParentChanged()
{
transform.parent = m_CachedParent;
AuthorityAppliedParenting = false;
Debug.LogException(new InvalidParentException($"[{name}] Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent"));
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent");
}
return;
}
else if (!parentObject.IsSpawned)
{
transform.parent = m_CachedParent;
AuthorityAppliedParenting = false;
Debug.LogException(new SpawnStateException($"[{name}] {nameof(NetworkObject)} can only be re-parented under another spawned {nameof(NetworkObject)}"));
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] {nameof(NetworkObject)} can only be re-parented under another spawned {nameof(NetworkObject)}.");
}
return;
}

Expand Down Expand Up @@ -2536,7 +2583,10 @@ internal void InvokeBehaviourNetworkSpawn()
{
if (!childBehaviour.gameObject.activeInHierarchy)
{
Debug.LogWarning($"{GenerateDisabledNetworkBehaviourWarning(childBehaviour)}");
if (NetworkManagerOwner.LogLevel <= LogLevel.Developer)
{
NetworkLog.LogWarning($"{GenerateDisabledNetworkBehaviourWarning(childBehaviour)}");
}
continue;
}
childBehaviour.InternalOnNetworkSpawn();
Expand Down Expand Up @@ -3327,7 +3377,11 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject,
// Spawn the NetworkObject
if (networkObject.IsSpawned)
{
throw new SpawnStateException($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!");
if (NetworkManager.Singleton.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!");
}
return null;
}

// Invoke the non-authority local spawn method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Unity.Netcode
/// <summary>
/// Exception thrown when the operation require NetworkManager to be listening.
/// </summary>
[Obsolete("Not used anymore.")]
public class NotListeningException : Exception
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -892,5 +892,32 @@ public IEnumerator NetworkShowAndChangeOwnership()
AssertOnTimeout($"Timed out waiting for clients-{m_NewOwner.LocalClientId} to gain ownership of object {m_OwnershipNetworkObject.NetworkObjectId}!");
VerboseDebug($"Client {m_NewOwner.LocalClientId} now owns object {m_OwnershipNetworkObject.NetworkObjectId}!");
}

[UnityTest]
public IEnumerator DuplicateHideShowTest()
{
var authority = GetAuthorityNetworkManager();
var nonAuthority = GetNonAuthorityNetworkManager();
m_ClientId0 = nonAuthority.LocalClientId;
ShowHideObject.ClientTargetedNetworkObjects.Clear();
ShowHideObject.ClientIdToTarget = m_ClientId0;
ShowHideObject.Silent = true;

var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority);
m_NetSpawnedObject1 = spawnedObject1.GetComponent<NetworkObject>();

m_NetSpawnedObject1.GetComponent<ShowHideObject>().MyNetworkVariable.Value++;
m_NetSpawnedObject1.NetworkHide(m_ClientId0);
m_NetSpawnedObject1.NetworkHide(m_ClientId0);

yield return WaitForConditionOrTimeOut(() => !nonAuthority.SpawnManager.SpawnedObjects.ContainsKey(m_NetSpawnedObject1.NetworkObjectId));
AssertOnTimeout($"NetworkObject {m_NetSpawnedObject1.name} is still spawned on client-{nonAuthority.LocalClientId} after timeout!");

m_NetSpawnedObject1.NetworkShow(m_ClientId0);
m_NetSpawnedObject1.NetworkShow(m_ClientId0);

yield return WaitForConditionOrTimeOut(() => nonAuthority.SpawnManager.SpawnedObjects.ContainsKey(m_NetSpawnedObject1.NetworkObjectId));
AssertOnTimeout($"NetworkObject {m_NetSpawnedObject1.name} is not yet spawned on client-{nonAuthority.LocalClientId} after timeout!");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public void SetupSet(Transform rootTransform, int setIndex, NetworkManager netwo
Assert.That(m_Pickup_NetObjs[setIndex], Is.Not.Null);
Assert.That(m_Pickup_Back_NetObjs[setIndex], Is.Not.Null);

LogAssert.Expect(LogType.Exception, new Regex("start a server or host", RegexOptions.IgnoreCase));
LogAssert.Expect(LogType.Error, new Regex("start a server or host", RegexOptions.IgnoreCase));
var cachedParent = m_Cube_NetObjs[setIndex].parent;
m_Cube_NetObjs[setIndex].parent = m_Pickup_NetObjs[setIndex];
Assert.That(m_Cube_NetObjs[setIndex].parent, Is.EqualTo(cachedParent), $"Transform {m_Cube_NetObjs[setIndex].parent.name} is not equal to transform {cachedParent.name}");
Expand Down