Skip to content

Commit 1a16947

Browse files
authored
fix: Update NetworkBehaviour on session owner promotion (#3817)
* fix: Update NetworkBehaviour on Session Owner promotion * Update CHANGELOG * Add a test for resetting extended ownership flags * Update CHANGELOG
1 parent 33df318 commit 1a16947

File tree

7 files changed

+331
-59
lines changed

7 files changed

+331
-59
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
4949

5050
### Fixed
5151

52+
- Ensure `NetworkBehaviour.IsSessionOwner` is correctly set when a new session owner is promoted. (#3817)
53+
- Reset extended ownership flags on `NetworkObject` despawn. (#3817)
5254
- Fixed issues with the "Client-server quickstart for Netcode for GameObjects" script having static methods and properties. (#3787)
5355
- Fixed issue where a warning message was being logged upon a client disconnecting from a server when the log level is set to developer. (#3786)
5456
- Fixed issue where the server or host would no longer have access to the transport id to client id table when processing a transport level client disconnect event. (#3786)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ public NetworkManager NetworkManager
515515
/// <summary>
516516
/// Gets whether the client is the distributed authority mode session owner.
517517
/// </summary>
518-
public bool IsSessionOwner { get; private set; }
518+
public bool IsSessionOwner { get; internal set; }
519519

520520
/// <summary>
521521
/// Gets whether the server (local or remote) is a host.

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,19 +216,21 @@ internal void HandleRedistributionToClients()
216216

217217
internal void SetSessionOwner(ulong sessionOwner)
218218
{
219-
var previousSessionOwner = CurrentSessionOwner;
220219
CurrentSessionOwner = sessionOwner;
221-
LocalClient.IsSessionOwner = LocalClientId == sessionOwner;
222-
if (LocalClient.IsSessionOwner)
220+
var isSessionOwner = LocalClientId == sessionOwner;
221+
LocalClient.IsSessionOwner = isSessionOwner;
222+
223+
foreach (var networkObject in SpawnManager.SpawnedObjects.Values)
223224
{
224-
foreach (var networkObjectEntry in SpawnManager.SpawnedObjects)
225+
if (isSessionOwner)
225226
{
226-
var networkObject = networkObjectEntry.Value;
227227
if (networkObject.IsOwnershipSessionOwner && networkObject.OwnerClientId != LocalClientId)
228228
{
229229
SpawnManager.ChangeOwnership(networkObject, LocalClientId, true);
230230
}
231231
}
232+
233+
networkObject.InvokeSessionOwnerPromoted(isSessionOwner);
232234
}
233235

234236
OnSessionOwnerPromoted?.Invoke(sessionOwner);

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2002,6 +2002,7 @@ internal void ResetOnDespawn()
20022002
IsSpawned = false;
20032003
DeferredDespawnTick = 0;
20042004
m_LatestParent = null;
2005+
RemoveOwnershipExtended(OwnershipStatusExtended.Locked | OwnershipStatusExtended.Requested);
20052006
}
20062007

20072008
/// <summary>
@@ -2078,6 +2079,19 @@ internal void InvokeOwnershipChanged(ulong previous, ulong next)
20782079
}
20792080
}
20802081

2082+
internal void InvokeSessionOwnerPromoted(bool isSessionOwner)
2083+
{
2084+
if (!IsSpawned)
2085+
{
2086+
return;
2087+
}
2088+
2089+
foreach (var childBehaviour in ChildNetworkBehaviours)
2090+
{
2091+
childBehaviour.IsSessionOwner = isSessionOwner;
2092+
}
2093+
}
2094+
20812095
internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
20822096
{
20832097
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
Lines changed: 144 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
25
using NUnit.Framework;
36
using Unity.Netcode.TestHelpers.Runtime;
47
using UnityEngine;
58
using UnityEngine.TestTools;
69

710
namespace Unity.Netcode.RuntimeTests
811
{
9-
[TestFixture(HostOrServer.DAHost, true)]
10-
[TestFixture(HostOrServer.DAHost, false)]
12+
internal enum SceneManagementSetting
13+
{
14+
EnableSceneManagement,
15+
DisableSceneManagement
16+
}
17+
18+
[TestFixture(SceneManagementSetting.EnableSceneManagement)]
19+
[TestFixture(SceneManagementSetting.DisableSceneManagement)]
1120
internal class ExtendedNetworkShowAndHideTests : NetcodeIntegrationTest
1221
{
1322
protected override int NumberOfClients => 3;
14-
private bool m_EnableSceneManagement;
23+
private readonly bool m_EnableSceneManagement;
1524
private GameObject m_ObjectToSpawn;
16-
private NetworkObject m_SpawnedObject;
17-
private NetworkManager m_ClientToHideFrom;
18-
private NetworkManager m_LateJoinClient;
19-
private NetworkManager m_SpawnOwner;
20-
21-
// TODO: [CmbServiceTests] Adapt to run with the service (can't control who becomes the session owner, needs a logic rework)
22-
/// <inheritdoc/>
23-
protected override bool UseCMBService()
24-
{
25-
return false;
26-
}
25+
private List<NetworkObject> m_SpawnedObjects = new();
26+
private Dictionary<NetworkManager, NetworkObject> m_ObjectHiddenFromClient = new();
2727

28-
public ExtendedNetworkShowAndHideTests(HostOrServer hostOrServer, bool enableSceneManagement) : base(hostOrServer)
28+
public ExtendedNetworkShowAndHideTests(SceneManagementSetting sceneManagement) : base(HostOrServer.DAHost)
2929
{
30-
m_EnableSceneManagement = enableSceneManagement;
30+
m_EnableSceneManagement = sceneManagement == SceneManagementSetting.EnableSceneManagement;
3131
}
3232

3333
protected override void OnServerAndClientsCreated()
@@ -38,48 +38,93 @@ protected override void OnServerAndClientsCreated()
3838
}
3939

4040
m_ObjectToSpawn = CreateNetworkObjectPrefab("TestObject");
41+
m_ObjectToSpawn.AddComponent<EmptyBehaviour>();
4142
m_ObjectToSpawn.SetActive(false);
4243

4344
base.OnServerAndClientsCreated();
4445
}
4546

46-
private bool AllClientsSpawnedObject()
47+
private bool AllObjectsHiddenFromTheirClient(StringBuilder errorLog)
4748
{
48-
foreach (var client in m_NetworkManagers)
49+
var allHidden = true;
50+
foreach (var (clientToHideFrom, objectToHide) in m_ObjectHiddenFromClient)
4951
{
50-
if (!s_GlobalNetworkObjects.ContainsKey(client.LocalClientId))
52+
if (clientToHideFrom.SpawnManager.SpawnedObjects.ContainsKey(objectToHide.NetworkObjectId))
5153
{
52-
return false;
53-
}
54-
if (!s_GlobalNetworkObjects[client.LocalClientId].ContainsKey(m_SpawnedObject.NetworkObjectId))
55-
{
56-
return false;
54+
errorLog.AppendLine($"Object-{objectToHide.NetworkObjectId} is still visible to Client-{clientToHideFrom.LocalClientId}");
55+
allHidden = false;
5756
}
5857
}
59-
return true;
58+
59+
return allHidden;
6060
}
6161

62-
private bool IsClientPromotedToSessionOwner()
62+
private bool IsClientPromotedToSessionOwner(StringBuilder errorLog)
6363
{
64+
var valid = true;
65+
var currentSessionOwner = GetAuthorityNetworkManager();
6466
foreach (var client in m_NetworkManagers)
6567
{
66-
if (!client.IsConnectedClient)
68+
if (currentSessionOwner == client)
6769
{
68-
continue;
70+
if (!client.LocalClient.IsSessionOwner)
71+
{
72+
errorLog.AppendLine($"Session owner is Client-{client.LocalClientId} but they are not marked as session owner.");
73+
valid = false;
74+
}
6975
}
70-
if (client.CurrentSessionOwner != m_ClientToHideFrom.LocalClientId)
76+
else if (client.LocalClient.IsSessionOwner)
7177
{
72-
return false;
78+
errorLog.AppendLine($"Client-{client.LocalClientId} is incorrectly marked as Session Owner.");
79+
valid = false;
80+
}
81+
82+
if (client.CurrentSessionOwner != currentSessionOwner.LocalClientId)
83+
{
84+
errorLog.AppendLine($"Client-{client.LocalClientId} has the incorrect session owner id (Has: {client.CurrentSessionOwner}, expected: {currentSessionOwner.LocalClientId}).");
85+
valid = false;
7386
}
7487
}
75-
return true;
88+
return valid;
7689
}
7790

78-
protected override void OnNewClientCreated(NetworkManager networkManager)
91+
private bool AllObjectsSpawnedExceptForHidden(StringBuilder errorLog)
7992
{
80-
m_LateJoinClient = networkManager;
81-
networkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement;
82-
base.OnNewClientCreated(networkManager);
93+
var valid = true;
94+
foreach (var client in m_NetworkManagers)
95+
{
96+
foreach (var spawnedObject in m_SpawnedObjects)
97+
{
98+
var isVisible = client.SpawnManager.SpawnedObjects.ContainsKey(spawnedObject.NetworkObjectId);
99+
100+
if (m_ObjectHiddenFromClient.TryGetValue(client, out var hiddenObject) && hiddenObject == spawnedObject)
101+
{
102+
if (isVisible)
103+
{
104+
errorLog.AppendLine($"Client-{client.LocalClientId} can see object-{spawnedObject.NetworkObjectId} that should be hidden.");
105+
valid = false;
106+
continue;
107+
}
108+
}
109+
else if (!isVisible)
110+
{
111+
errorLog.AppendLine($"Client-{client.LocalClientId} hasn't spawned object-{spawnedObject.NetworkObjectId}");
112+
valid = false;
113+
}
114+
115+
if (isVisible)
116+
{
117+
var clientObject = client.SpawnManager.SpawnedObjects[spawnedObject.NetworkObjectId];
118+
var behaviour = clientObject.GetComponent<EmptyBehaviour>();
119+
if (behaviour.IsSessionOwner != client.LocalClient.IsSessionOwner)
120+
{
121+
errorLog.AppendLine($"Client-{client.LocalClientId} network behaviour has incorrect session owner value. Should be {client.LocalClient.IsSessionOwner}, is {behaviour.IsSessionOwner}");
122+
valid = false;
123+
}
124+
}
125+
}
126+
}
127+
return valid;
83128
}
84129

85130
/// <summary>
@@ -95,41 +140,89 @@ public IEnumerator HiddenObjectPromotedSessionOwnerNewClientSynchronizes()
95140
{
96141
// Get the test relative session owner
97142
var sessionOwner = GetAuthorityNetworkManager();
98-
m_SpawnOwner = m_UseCmbService ? m_ClientNetworkManagers[1] : m_ClientNetworkManagers[0];
99-
m_ClientToHideFrom = m_UseCmbService ? m_ClientNetworkManagers[NumberOfClients - 1] : m_ClientNetworkManagers[1];
143+
144+
// Spawn objects for the non-session owner clients
100145
m_ObjectToSpawn.SetActive(true);
146+
foreach (var client in m_NetworkManagers)
147+
{
148+
if (client == sessionOwner)
149+
{
150+
Assert.IsTrue(client.LocalClient.IsSessionOwner);
151+
continue;
152+
}
153+
154+
var instance = SpawnObject(m_ObjectToSpawn, client).GetComponent<NetworkObject>();
155+
m_SpawnedObjects.Add(instance);
156+
}
157+
158+
yield return WaitForSpawnedOnAllOrTimeOut(m_SpawnedObjects);
159+
AssertOnTimeout("[InitialSpawn] Not all clients spawned all objects");
160+
161+
// Hide one spawned object from each client
162+
var setOfInstances = m_SpawnedObjects.ToHashSet();
163+
foreach (var clientToHideFrom in m_NetworkManagers)
164+
{
165+
// Session owner doesn't need to have an object hidden from them
166+
if (clientToHideFrom == sessionOwner)
167+
{
168+
continue;
169+
}
170+
171+
// Find an object that this client doesn't own that isn't already hidden from another client
172+
var toHide = setOfInstances.Last(obj => obj.OwnerClientId != clientToHideFrom.LocalClientId);
173+
toHide.NetworkHide(clientToHideFrom.LocalClientId);
174+
setOfInstances.Remove(toHide);
175+
m_ObjectHiddenFromClient.Add(clientToHideFrom, toHide);
101176

102-
// Spawn the object with a non-session owner client
103-
m_SpawnedObject = SpawnObject(m_ObjectToSpawn, m_SpawnOwner).GetComponent<NetworkObject>();
104-
yield return WaitForConditionOrTimeOut(AllClientsSpawnedObject);
105-
AssertOnTimeout($"Not all clients spawned and instance of {m_SpawnedObject.name}");
177+
Assert.IsFalse(toHide.GetComponent<EmptyBehaviour>().IsSessionOwner, "No object should have been spawned owned by the session owner");
178+
}
106179

107-
// Hide the spawned object from the to be promoted session owner
108-
m_SpawnedObject.NetworkHide(m_ClientToHideFrom.LocalClientId);
180+
Assert.That(m_ObjectHiddenFromClient.Count, Is.EqualTo(NumberOfClients), "Test should hide one object per non-authority client");
181+
Assert.That(setOfInstances, Is.Empty, "Not all objects have been hidden frm someone.");
109182

110-
yield return WaitForConditionOrTimeOut(() => !m_ClientToHideFrom.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedObject.NetworkObjectId));
111-
AssertOnTimeout($"{m_SpawnedObject.name} was not hidden from Client-{m_ClientToHideFrom.LocalClientId}!");
183+
yield return WaitForConditionOrTimeOut(AllObjectsHiddenFromTheirClient);
184+
AssertOnTimeout("Not all objects have been hidden from someone!");
112185

113186
// Promoted a new session owner (DAHost promotes while CMB Session we disconnect the current session owner)
114187
if (!m_UseCmbService)
115188
{
116-
m_ServerNetworkManager.PromoteSessionOwner(m_ClientToHideFrom.LocalClientId);
189+
var nonAuthority = GetNonAuthorityNetworkManager();
190+
m_ServerNetworkManager.PromoteSessionOwner(nonAuthority.LocalClientId);
191+
yield return s_DefaultWaitForTick;
117192
}
118193
else
119194
{
120-
sessionOwner.Shutdown();
195+
yield return StopOneClient(sessionOwner);
121196
}
122197

123198
// Wait for the new session owner to be promoted and for all clients to acknowledge the promotion
124199
yield return WaitForConditionOrTimeOut(IsClientPromotedToSessionOwner);
125-
AssertOnTimeout($"Client-{m_ClientToHideFrom.LocalClientId} was not promoted as session owner on all client instances!");
200+
AssertOnTimeout($"No client was promoted as session owner on all client instances!");
201+
202+
var newSessionOwner = GetAuthorityNetworkManager();
203+
VerboseDebug($"Client-{newSessionOwner.LocalClientId} was promoted as session owner on all client instances!");
204+
Assert.That(newSessionOwner, Is.Not.EqualTo(sessionOwner), "The current session owner be different from the original session owner.");
205+
Assert.That(m_ObjectHiddenFromClient, Does.ContainKey(newSessionOwner), "An object should be hidden from the newly promoted session owner");
126206

127207
// Connect a new client instance
128-
yield return CreateAndStartNewClient();
208+
var newClient = CreateNewClient();
209+
newClient.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement;
210+
yield return StartClient(newClient);
129211

130212
// Assure the newly connected client is synchronized with the NetworkObject hidden from the newly promoted session owner
131-
yield return WaitForConditionOrTimeOut(() => m_LateJoinClient.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedObject.NetworkObjectId));
132-
AssertOnTimeout($"Client-{m_LateJoinClient.LocalClientId} never spawned {nameof(NetworkObject)} {m_SpawnedObject.name}!");
213+
yield return WaitForConditionOrTimeOut(AllObjectsSpawnedExceptForHidden);
214+
AssertOnTimeout("[LateJoinClient] Not all objects spawned correctly");
215+
}
216+
217+
protected override IEnumerator OnTearDown()
218+
{
219+
m_SpawnedObjects.Clear();
220+
m_ObjectHiddenFromClient.Clear();
221+
return base.OnTearDown();
222+
}
223+
224+
private class EmptyBehaviour : NetworkBehaviour
225+
{
133226
}
134227
}
135228
}

0 commit comments

Comments
 (0)