Skip to content

Commit feb09e5

Browse files
Merge branch 'develop-2.0.0' into renovate/develop-2.0.0-package-ci-ubuntu-22.04-4.x
2 parents c17adad + eb9de17 commit feb09e5

File tree

5 files changed

+202
-7
lines changed

5 files changed

+202
-7
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

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

2525
### Fixed
2626

27+
- Fixed issue when using a client-server topology where a `NetworkList` with owner write permissions was resetting sent time and dirty flags after having been spawned on owning clients that were not the spawn authority. (#3850)
2728
- Fixed an integer overflow that occurred when configuring a large disconnect timeout with Unity Transport. (#3810)
2829

30+
2931
### Security
3032

3133

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ public uint PrefabIdHash
7676
/// </remarks>
7777
public List<NetworkTransform> NetworkTransforms { get; private set; }
7878

79+
/// <summary>
80+
/// Set to true if this instance is the original/first instance created and spawned which
81+
/// means the <see cref="CreateObjectMessage"/> was generated from this instance and sent
82+
/// to all other clients.
83+
///
84+
/// Client-Server: This will be true for all instances on the server or host.
85+
/// Distributed Authority: This will be true on the client that created the first instance
86+
/// and spawned it (even if spawning with ownership being assigned to a different client).
87+
/// </summary>
88+
internal bool IsSpawnAuthority;
7989

8090
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
8191
/// <summary>
@@ -2009,6 +2019,7 @@ internal void ResetOnDespawn()
20092019
{
20102020
// Always clear out the observers list when despawned
20112021
Observers.Clear();
2022+
IsSpawnAuthority = false;
20122023
IsSpawned = false;
20132024
DeferredDespawnTick = 0;
20142025
m_LatestParent = null;

com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,18 @@ public NetworkList(IEnumerable<T> values = default,
6060

6161
internal override void OnSpawned()
6262
{
63-
// If we are dirty and have write permissions by the time the NetworkObject
64-
// is finished spawning (same frame), then go ahead and reset the dirty related
65-
// properties for NetworkList in the event user script has made changes when
66-
// spawning to prevent duplicate entries.
67-
if (IsDirty() && CanSend())
63+
// If the NetworkList is:
64+
// - Dirty
65+
// - State updates can be sent:
66+
// -- The instance has write permissions.
67+
// -- The last sent time plus the max send time period is less than the current time.
68+
// - User script has modified the list during spawn.
69+
// - This instance is on the spawn authority side.
70+
// When the NetworkObject is finished spawning (on the same frame), go ahead and reset
71+
// the dirty related properties and last sent time to prevent duplicate entries from
72+
// being sent (i.e. CreateObjectMessage will contain the changes so we don't need to
73+
// send a proceeding NetworkVariableDeltaMessage).
74+
if (IsDirty() && CanSend() && m_NetworkObject.IsSpawnAuthority)
6875
{
6976
UpdateLastSentTime();
7077
ResetDirty();

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,9 @@ internal void AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n
10541054
Debug.LogError("Spawning NetworkObjects with nested NetworkObjects is only supported for scene objects. Child NetworkObjects will not be spawned over the network!");
10551055
}
10561056
}
1057+
1058+
networkObject.IsSpawnAuthority = true;
1059+
10571060
// Invoke NetworkBehaviour.OnPreSpawn methods
10581061
networkObject.NetworkManagerOwner = NetworkManager;
10591062
networkObject.InvokeBehaviourNetworkPreSpawn();

com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ public NetworkListTests(HostOrServer host) : base(host) { }
2727

2828
private ulong m_TestObjectId;
2929

30+
protected override IEnumerator OnSetup()
31+
{
32+
IsOwnerWriteTest = false;
33+
return base.OnSetup();
34+
}
35+
3036
protected override void OnServerAndClientsCreated()
3137
{
3238
m_ListObjectPrefab = CreateNetworkObjectPrefab("ListObject");
@@ -285,13 +291,169 @@ private int[] Shuffle(List<int> list)
285291
// This will do a shuffle of the list
286292
return list.OrderBy(_ => rng.Next()).ToArray();
287293
}
294+
295+
private List<NetworkObject> m_SpawnedObjects = new List<NetworkObject>();
296+
internal const int ValueCount = 10;
297+
internal static bool IsOwnerWriteTest;
298+
internal NetworkManager LateJoinedClient;
299+
internal static List<int> OwnerWriteExpectedValues = new List<int>();
300+
301+
protected override void OnNewClientCreated(NetworkManager networkManager)
302+
{
303+
if (IsOwnerWriteTest)
304+
{
305+
LateJoinedClient = networkManager;
306+
}
307+
else
308+
{
309+
LateJoinedClient = null;
310+
}
311+
base.OnNewClientCreated(networkManager);
312+
}
313+
314+
[UnityTest]
315+
public IEnumerator OwnerWriteTests()
316+
{
317+
IsOwnerWriteTest = true;
318+
var authorityNetworkManager = GetAuthorityNetworkManager();
319+
m_SpawnedObjects.Clear();
320+
OwnerWriteExpectedValues.Clear();
321+
// Set our initial expected values as 0 - 9
322+
for (int i = 0; i < ValueCount; i++)
323+
{
324+
OwnerWriteExpectedValues.Add(i);
325+
}
326+
327+
// Each spawned instance will be owned by each NetworkManager instance in order
328+
// to validate owner write NetworkLists.
329+
foreach (var networkManager in m_NetworkManagers)
330+
{
331+
m_SpawnedObjects.Add(SpawnObject(m_ListObjectPrefab, networkManager).GetComponent<NetworkObject>());
332+
}
333+
334+
// Verify all NetworkManager instances spawned the objects
335+
yield return WaitForSpawnedOnAllOrTimeOut(m_SpawnedObjects);
336+
AssertOnTimeout("Not all instances were spawned on all clients!");
337+
338+
// Verify all spawned object instances have the expected owner write NetworkList values
339+
yield return WaitForConditionOrTimeOut(OnVerifyOwnerWriteData);
340+
AssertOnTimeout("Detected invalid count or value on one of the spawned instances!");
341+
342+
// Late join a client
343+
yield return CreateAndStartNewClient();
344+
345+
// Spawn an instance with the new client being the owner
346+
m_SpawnedObjects.Add(SpawnObject(m_ListObjectPrefab, LateJoinedClient).GetComponent<NetworkObject>());
347+
348+
// Verify all NetworkManager instances spawned the objects
349+
yield return WaitForSpawnedOnAllOrTimeOut(m_SpawnedObjects);
350+
AssertOnTimeout("Not all instances were spawned on all clients!");
351+
352+
// Verify all spawned object instances have the expected owner write NetworkList values
353+
yield return WaitForConditionOrTimeOut(OnVerifyOwnerWriteData);
354+
AssertOnTimeout("Detected invalid count or value on one of the spawned instances!");
355+
356+
// Now have all of the clients update their list values to randomly assigned values
357+
// in order to verify changes to owner write NetworkLists are synchronized properly.
358+
OwnerWriteExpectedValues.Clear();
359+
for (int i = 0; i < ValueCount; i++)
360+
{
361+
OwnerWriteExpectedValues.Add(Random.Range(10, 100));
362+
}
363+
UpdateOwnerWriteValues();
364+
365+
// Verify all spawned object instances have the expected owner write NetworkList values
366+
yield return WaitForConditionOrTimeOut(OnVerifyOwnerWriteData);
367+
AssertOnTimeout("Detected invalid count or value on one of the spawned instances!");
368+
369+
// Verifies that spawning with ownership in distributed authority mode work properly.
370+
// Where:
371+
// Client-A spawns with ownership assigned to Client-B
372+
// Client-B applies values at spawn.
373+
// All clients then should be updated with those new values applied.
374+
if (m_DistributedAuthority)
375+
{
376+
var prefabNetworkObject = m_ListObjectPrefab.GetComponent<NetworkObject>();
377+
foreach (var networkManager in m_NetworkManagers)
378+
{
379+
var instance = Object.Instantiate(m_ListObjectPrefab).GetComponent<NetworkObject>();
380+
SpawnInstanceWithOwnership(instance, authorityNetworkManager, networkManager.LocalClientId);
381+
m_SpawnedObjects.Add(instance);
382+
}
383+
384+
// Verify all NetworkManager instances spawned the objects
385+
yield return WaitForSpawnedOnAllOrTimeOut(m_SpawnedObjects);
386+
AssertOnTimeout("Not all instances were spawned on all clients!");
387+
388+
// Verify all spawned object instances have the expected owner write NetworkList values
389+
yield return WaitForConditionOrTimeOut(OnVerifyOwnerWriteData);
390+
AssertOnTimeout("Detected invalid count or value on one of the spawned instances!");
391+
}
392+
}
393+
394+
private void UpdateOwnerWriteValues()
395+
{
396+
foreach (var spawnedObject in m_SpawnedObjects)
397+
{
398+
var owningNetworkManager = m_NetworkManagers.Where((c) => c.LocalClientId == spawnedObject.OwnerClientId).First();
399+
var networkObjectId = spawnedObject.NetworkObjectId;
400+
var listComponent = owningNetworkManager.SpawnManager.SpawnedObjects[networkObjectId].GetComponent<NetworkListTest>();
401+
for (int i = 0; i < ValueCount; i++)
402+
{
403+
listComponent.OwnerWriteList[i] = OwnerWriteExpectedValues[i];
404+
}
405+
}
406+
}
407+
408+
private bool OnVerifyOwnerWriteData(StringBuilder errorLog)
409+
{
410+
foreach (var spawnedObject in m_SpawnedObjects)
411+
{
412+
var networkObjectId = spawnedObject.NetworkObjectId;
413+
foreach (var networkManager in m_NetworkManagers)
414+
{
415+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
416+
{
417+
errorLog.Append($"[Client-{networkManager.LocalClientId}] Does not have an instance of spawned object NetworkObjectId: {networkObjectId}");
418+
return false;
419+
}
420+
var listComponent = networkManager.SpawnManager.SpawnedObjects[networkObjectId].GetComponent<NetworkListTest>();
421+
422+
if (listComponent == null)
423+
{
424+
errorLog.Append($"[Client-{networkManager.LocalClientId}] List component was not found");
425+
return false;
426+
}
427+
428+
if (listComponent.OwnerWriteList.Count != ValueCount)
429+
{
430+
errorLog.Append($"[Client-{networkManager.LocalClientId}] List component has the incorrect number of items. Expected: {ValueCount}, Have: {listComponent.TheList.Count}");
431+
return false;
432+
}
433+
434+
for (int i = 0; i < ValueCount; i++)
435+
{
436+
var actual = listComponent.OwnerWriteList[i];
437+
var expected = OwnerWriteExpectedValues[i];
438+
if (expected != actual)
439+
{
440+
errorLog.Append($"[Client-{networkManager.LocalClientId}] Incorrect value at index {i}, expected: {expected}, actual: {actual}");
441+
return false;
442+
}
443+
}
444+
}
445+
}
446+
447+
return true;
448+
}
288449
}
289450

290451
internal class NetworkListTest : NetworkBehaviour
291452
{
292453
public readonly NetworkList<int> TheList = new();
293454
public readonly NetworkList<StructUsedOnlyInNetworkList> TheStructList = new();
294455
public readonly NetworkList<FixedString128Bytes> TheLargeList = new();
456+
public readonly NetworkList<int> OwnerWriteList = new NetworkList<int>(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
295457

296458
private void ListChanged(NetworkListEvent<int> e)
297459
{
@@ -309,6 +471,18 @@ public override void OnDestroy()
309471
base.OnDestroy();
310472
}
311473

474+
public override void OnNetworkSpawn()
475+
{
476+
if (NetworkListTests.IsOwnerWriteTest && IsOwner)
477+
{
478+
for (int i = 0; i < NetworkListTests.ValueCount; i++)
479+
{
480+
OwnerWriteList.Add(NetworkListTests.OwnerWriteExpectedValues[i]);
481+
}
482+
}
483+
base.OnNetworkSpawn();
484+
}
485+
312486
public bool ListDelegateTriggered;
313487
}
314488

@@ -325,8 +499,6 @@ internal class NetworkListTestPredicate : ConditionalPredicateBase
325499

326500
private readonly NetworkListTest m_NonAuthorityInstance;
327501

328-
private string m_TestStageFailedMessage;
329-
330502
/// <summary>
331503
/// Determines if the condition has been reached for the current NetworkListTestState
332504
/// </summary>

0 commit comments

Comments
 (0)