Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
2 changes: 2 additions & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- 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)
- Fixed an integer overflow that occurred when configuring a large disconnect timeout with Unity Transport. (#3810)


### Security


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,18 @@ public NetworkList(IEnumerable<T> values = default,

internal override void OnSpawned()
{
// If we are dirty and have write permissions by the time the NetworkObject
// is finished spawning (same frame), then go ahead and reset the dirty related
// properties for NetworkList in the event user script has made changes when
// spawning to prevent duplicate entries.
if (IsDirty() && CanSend())
// If the NetworkList is:
// - Dirty
// - State updates can be sent:
// -- The instance has write permissions.
// -- The last sent time plus the max send time period is less than the current time.
// - User script has modified the list during spawn.
// - This instance is on the spawn authority side.
// When the NetworkObject is finished spawning (on the same frame), go ahead and reset
// the dirty related properties and last sent time to prevent duplicate entries from
// being sent (i.e. CreateObjectMessage will contain the changes so we don't need to
// send a proceeding NetworkVariableDeltaMessage).
if (IsDirty() && CanSend() && m_NetworkBehaviour.HasAuthority)
{
UpdateLastSentTime();
ResetDirty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public NetworkListTests(HostOrServer host) : base(host) { }

private ulong m_TestObjectId;

protected override IEnumerator OnSetup()
{
IsOwnerWriteTest = false;
return base.OnSetup();
}

protected override void OnServerAndClientsCreated()
{
m_ListObjectPrefab = CreateNetworkObjectPrefab("ListObject");
Expand Down Expand Up @@ -285,13 +291,144 @@ private int[] Shuffle(List<int> list)
// This will do a shuffle of the list
return list.OrderBy(_ => rng.Next()).ToArray();
}

private List<NetworkObject> m_SpawnedObjects = new List<NetworkObject>();
internal const int SpawnCount = 10;
internal bool IsOwnerWriteTest;
internal NetworkManager LateJoinedClient;

protected override void OnNewClientCreated(NetworkManager networkManager)
{
if (IsOwnerWriteTest)
{
LateJoinedClient = networkManager;
}
else
{
LateJoinedClient = null;
}
base.OnNewClientCreated(networkManager);
}

[UnityTest]
public IEnumerator OwnerWriteTests()
{
IsOwnerWriteTest = true;
var authorityBetworkManager = GetAuthorityNetworkManager();
m_SpawnedObjects.Clear();
m_ExpectedValues.Clear();
// Set our initial expected values as 0 - 9
for (int i = 0; i < SpawnCount; i++)
{
m_ExpectedValues.Add(i);
}

// Each spawned instance will be owned by each NetworkManager instance in order
// to validate owner write NetworkLists.
foreach (var networkManager in m_NetworkManagers)
{
m_SpawnedObjects.Add(SpawnObject(m_ListObjectPrefab, networkManager).GetComponent<NetworkObject>());
}

// Verify all NetworkManager instances spawned the objects
yield return WaitForSpawnedOnAllOrTimeOut(m_SpawnedObjects);
AssertOnTimeout("Not all instances were spawned on all clients!");

// Verify all spawned object instances have the expected owner write NetworkList values
yield return WaitForConditionOrTimeOut(OnVerifyOwnerWriteData);
AssertOnTimeout("Detected invalid count or value on one of the spawned instances!");

// Late join a client
yield return CreateAndStartNewClient();

// Spawn an instance with the new client being the owner
m_SpawnedObjects.Add(SpawnObject(m_ListObjectPrefab, LateJoinedClient).GetComponent<NetworkObject>());

// Verify all NetworkManager instances spawned the objects
yield return WaitForSpawnedOnAllOrTimeOut(m_SpawnedObjects);
AssertOnTimeout("Not all instances were spawned on all clients!");

// Verify all spawned object instances have the expected owner write NetworkList values
yield return WaitForConditionOrTimeOut(OnVerifyOwnerWriteData);
AssertOnTimeout("Detected invalid count or value on one of the spawned instances!");

// Now have all of the clients update their list values to randomly assigned values
// in order to verify changes to owner write NetworkLists are synchronized properly.
m_ExpectedValues.Clear();
for (int i = 0; i < SpawnCount; i++)
{
m_ExpectedValues.Add(Random.Range(10, 100));
}
UpdateOwnerWriteValues();

// Verify all spawned object instances have the expected owner write NetworkList values
yield return WaitForConditionOrTimeOut(OnVerifyOwnerWriteData);
AssertOnTimeout("Detected invalid count or value on one of the spawned instances!");
}

private void UpdateOwnerWriteValues()
{
foreach (var spawnedObject in m_SpawnedObjects)
{
var owningNetworkManager = m_NetworkManagers.Where((c) => c.LocalClientId == spawnedObject.OwnerClientId).First();
var networkObjectId = spawnedObject.NetworkObjectId;
var listComponent = owningNetworkManager.SpawnManager.SpawnedObjects[networkObjectId].GetComponent<NetworkListTest>();
for (int i = 0; i < SpawnCount; i++)
{
listComponent.OwnerWriteList[i] = m_ExpectedValues[i];
}
}
}

private bool OnVerifyOwnerWriteData(StringBuilder errorLog)
{
foreach (var spawnedObject in m_SpawnedObjects)
{
var networkObjectId = spawnedObject.NetworkObjectId;
foreach (var networkManager in m_NetworkManagers)
{
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
{
errorLog.Append($"[Client-{networkManager.LocalClientId}] Does not have an instance of spawned object NetworkObjectId: {networkObjectId}");
return false;
}
var listComponent = networkManager.SpawnManager.SpawnedObjects[networkObjectId].GetComponent<NetworkListTest>();

if (listComponent == null)
{
errorLog.Append($"[Client-{networkManager.LocalClientId}] List component was not found");
return false;
}

if (listComponent.OwnerWriteList.Count != SpawnCount)
{
errorLog.Append($"[Client-{networkManager.LocalClientId}] List component has the incorrect number of items. Expected: {SpawnCount}, Have: {listComponent.TheList.Count}");
return false;
}

for (int i = 0; i < SpawnCount; i++)
{
var actual = listComponent.OwnerWriteList[i];
var expected = m_ExpectedValues[i];
if (expected != actual)
{
errorLog.Append($"[Client-{networkManager.LocalClientId}] Incorrect value at index {i}, expected: {expected}, actual: {actual}");
return false;
}
}
}
}

return true;
}
}

internal class NetworkListTest : NetworkBehaviour
{
public readonly NetworkList<int> TheList = new();
public readonly NetworkList<StructUsedOnlyInNetworkList> TheStructList = new();
public readonly NetworkList<FixedString128Bytes> TheLargeList = new();
public readonly NetworkList<int> OwnerWriteList = new NetworkList<int>(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);

private void ListChanged(NetworkListEvent<int> e)
{
Expand All @@ -309,6 +446,18 @@ public override void OnDestroy()
base.OnDestroy();
}

public override void OnNetworkSpawn()
{
if (IsOwner)
{
for (int i = 0; i < NetworkListTests.SpawnCount; i++)
{
OwnerWriteList.Add(i);
}
}
base.OnNetworkSpawn();
}

public bool ListDelegateTriggered;
}

Expand Down