Skip to content

Commit 0c32e93

Browse files
fix: networkvariable collections can be modified without write permissions [backport] (#3126)
* Update Backporting #3081 to NGO v1 * test Back porting #3081 test updates (where applicable). * update adding changelog entries * update Disposing the internal original. * fix Making sure the force send is passed along to the NetworkBehaviour
1 parent 773d5f3 commit 0c32e93

File tree

13 files changed

+1510
-163
lines changed

13 files changed

+1510
-163
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ Additional documentation and release notes are available at [Multiplayer Documen
1717

1818
### Fixed
1919

20+
- Fixed issue where a newly synchronizing client would be synchronized with the current `NetworkVariable` values always which could cause issues with collections if there were any pending state updates. Now, when initially synchronizing a client, if a `NetworkVariable` has a pending state update it will serialize the previously known value(s) to the synchronizing client so when the pending updates are sent they aren't duplicate values on the newly connected client side. (#3126)
21+
- Fixed issue where changing ownership would mark every `NetworkVariable` dirty. Now, it will only mark any `NetworkVariable` with owner read permissions as dirty and will send/flush any pending updates to all clients prior to sending the change in ownership message. (#3126)
22+
- Fixed issue with `NetworkVariable` collections where transferring ownership to another client would not update the new owner's previous value to the most current value which could cause the last/previous added value to be detected as a change when adding or removing an entry (as long as the entry removed was not the last/previously added value). (#3126)
23+
- Fixed issue where a client (or server) with no write permissions for a `NetworkVariable` using a standard .NET collection type could still modify the collection which could cause various issues depending upon the modification and collection type. (#3126)
2024
- Fixed issue where `NetworkAnimator` would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. (#3124)
2125
- Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3084)
2226
- Fixed issue where `NetworkAnimator` would send updates to non-observer clients. (#3058)
@@ -28,6 +32,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
2832

2933
### Changed
3034

35+
- Changed `NetworkVariableDeltaMessage` so the server now forwards delta state updates (owner write permission based from a client) to other clients immediately as opposed to keeping a `NetworkVariable` or `NetworkList` dirty and processing them at the end of the frame or potentially on the next network tick. (#3126)
3136
- The Debug Simulator section of the Unity Transport component will now be hidden if Unity Transport 2.0 or later is installed. It was already non-functional in that situation and users should instead use the more featureful [Network Simulator](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) tool from the Multiplayer Tools package. (#3120)
3237

3338

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

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,13 @@ public virtual void OnGainedOwnership() { }
765765
internal void InternalOnGainedOwnership()
766766
{
767767
UpdateNetworkProperties();
768+
// New owners need to assure any NetworkVariables they have write permissions
769+
// to are updated so the previous and original values are aligned with the
770+
// current value (primarily for collections).
771+
if (OwnerClientId == NetworkManager.LocalClientId)
772+
{
773+
UpdateNetworkVariableOnOwnershipChanged();
774+
}
768775
OnGainedOwnership();
769776
}
770777

@@ -946,20 +953,20 @@ internal void PreVariableUpdate()
946953
PreNetworkVariableWrite();
947954
}
948955

949-
internal void VariableUpdate(ulong targetClientId)
950-
{
951-
NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
952-
}
953-
954956
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
955957
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
956958

957-
private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex)
959+
internal void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex, bool forceSend = false)
958960
{
959-
if (!CouldHaveDirtyNetworkVariables())
961+
if (!forceSend && !CouldHaveDirtyNetworkVariables())
960962
{
961963
return;
962964
}
965+
// Getting these ahead of time actually improves performance
966+
var networkManager = NetworkManager;
967+
var networkObject = NetworkObject;
968+
var messageManager = networkManager.MessageManager;
969+
var connectionManager = networkManager.ConnectionManager;
963970

964971
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
965972
{
@@ -982,10 +989,14 @@ private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex)
982989
var message = new NetworkVariableDeltaMessage
983990
{
984991
NetworkObjectId = NetworkObjectId,
985-
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
992+
NetworkBehaviourIndex = networkObject.GetNetworkBehaviourOrderIndex(this),
986993
NetworkBehaviour = this,
987994
TargetClientId = targetClientId,
988-
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
995+
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j],
996+
// By sending the network delivery we can forward messages immediately as opposed to processing them
997+
// at the end. While this will send updates to clients that cannot read, the handler will ignore anything
998+
// sent to a client that does not have read permissions.
999+
NetworkDelivery = m_DeliveryTypesForNetworkVariableGroups[j]
9891000
};
9901001
// TODO: Serialization is where the IsDirty flag gets changed.
9911002
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
@@ -994,15 +1005,15 @@ private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex)
9941005
// so we don't have to do this serialization work if we're not going to use the result.
9951006
if (IsServer && targetClientId == NetworkManager.ServerClientId)
9961007
{
997-
var tmpWriter = new FastBufferWriter(NetworkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, NetworkManager.MessageManager.FragmentedMessageMaxSize);
1008+
var tmpWriter = new FastBufferWriter(messageManager.NonFragmentedMessageMaxSize, Allocator.Temp, messageManager.FragmentedMessageMaxSize);
9981009
using (tmpWriter)
9991010
{
10001011
message.Serialize(tmpWriter, message.Version);
10011012
}
10021013
}
10031014
else
10041015
{
1005-
NetworkManager.ConnectionManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId);
1016+
connectionManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId);
10061017
}
10071018
}
10081019
}
@@ -1029,6 +1040,26 @@ private bool CouldHaveDirtyNetworkVariables()
10291040
return false;
10301041
}
10311042

1043+
/// <summary>
1044+
/// Invoked on a new client to assure the previous and original values
1045+
/// are synchronized with the current known value.
1046+
/// </summary>
1047+
/// <remarks>
1048+
/// Primarily for collections to assure the previous value(s) is/are the
1049+
/// same as the current value(s) in order to not re-send already known entries.
1050+
/// </remarks>
1051+
internal void UpdateNetworkVariableOnOwnershipChanged()
1052+
{
1053+
for (int j = 0; j < NetworkVariableFields.Count; j++)
1054+
{
1055+
// Only invoke OnInitialize on NetworkVariables the owner can write to
1056+
if (NetworkVariableFields[j].CanClientWrite(OwnerClientId))
1057+
{
1058+
NetworkVariableFields[j].OnInitialize();
1059+
}
1060+
}
1061+
}
1062+
10321063
internal void MarkVariablesDirty(bool dirty)
10331064
{
10341065
for (int j = 0; j < NetworkVariableFields.Count; j++)
@@ -1037,6 +1068,17 @@ internal void MarkVariablesDirty(bool dirty)
10371068
}
10381069
}
10391070

1071+
internal void MarkOwnerReadVariablesDirty()
1072+
{
1073+
for (int j = 0; j < NetworkVariableFields.Count; j++)
1074+
{
1075+
if (NetworkVariableFields[j].ReadPerm == NetworkVariableReadPermission.Owner)
1076+
{
1077+
NetworkVariableFields[j].SetDirty(true);
1078+
}
1079+
}
1080+
}
1081+
10401082
/// <summary>
10411083
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
10421084
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
@@ -1067,15 +1109,23 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie
10671109
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
10681110
writer.WriteValueSafe((ushort)0);
10691111
var startPos = writer.Position;
1070-
NetworkVariableFields[j].WriteField(writer);
1112+
// Write the NetworkVariable field value
1113+
// WriteFieldSynchronization will write the current value only if there are no pending changes.
1114+
// Otherwise, it will write the previous value if there are pending changes since the pending
1115+
// changes will be sent shortly after the client's synchronization.
1116+
NetworkVariableFields[j].WriteFieldSynchronization(writer);
10711117
var size = writer.Position - startPos;
10721118
writer.Seek(writePos);
10731119
writer.WriteValueSafe((ushort)size);
10741120
writer.Seek(startPos + size);
10751121
}
10761122
else
10771123
{
1078-
NetworkVariableFields[j].WriteField(writer);
1124+
// Write the NetworkVariable field value
1125+
// WriteFieldSynchronization will write the current value only if there are no pending changes.
1126+
// Otherwise, it will write the previous value if there are pending changes since the pending
1127+
// changes will be sent shortly after the client's synchronization.
1128+
NetworkVariableFields[j].WriteFieldSynchronization(writer);
10791129
}
10801130
}
10811131
else // Only if EnsureNetworkVariableLengthSafety, otherwise just skip

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ public class NetworkBehaviourUpdater
1919

2020
internal void AddForUpdate(NetworkObject networkObject)
2121
{
22+
// Since this is a HashSet, we don't need to worry about duplicate entries
2223
m_PendingDirtyNetworkObjects.Add(networkObject);
2324
}
2425

25-
internal void NetworkBehaviourUpdate()
26+
/// <summary>
27+
/// Sends NetworkVariable deltas
28+
/// </summary>
29+
/// <param name="forceSend">internal only, when changing ownership we want to send this before the change in ownership message</param>
30+
internal void NetworkBehaviourUpdate(bool forceSend = false)
2631
{
2732
#if DEVELOPMENT_BUILD || UNITY_EDITOR
2833
m_NetworkBehaviourUpdate.Begin();
@@ -54,7 +59,7 @@ internal void NetworkBehaviourUpdate()
5459
// Sync just the variables for just the objects this client sees
5560
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
5661
{
57-
dirtyObj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
62+
dirtyObj.ChildNetworkBehaviours[k].NetworkVariableUpdate(client.ClientId, k, forceSend);
5863
}
5964
}
6065
}
@@ -73,7 +78,7 @@ internal void NetworkBehaviourUpdate()
7378
}
7479
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
7580
{
76-
sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId);
81+
sobj.ChildNetworkBehaviours[k].NetworkVariableUpdate(NetworkManager.ServerClientId, k, forceSend);
7782
}
7883
}
7984
}
@@ -86,19 +91,28 @@ internal void NetworkBehaviourUpdate()
8691
var behaviour = dirtyObj.ChildNetworkBehaviours[k];
8792
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++)
8893
{
94+
// Set to true for NetworkVariable to ignore duplication of the
95+
// "internal original value" for collections support.
96+
behaviour.NetworkVariableFields[i].NetworkUpdaterCheck = true;
8997
if (behaviour.NetworkVariableFields[i].IsDirty() &&
9098
!behaviour.NetworkVariableIndexesToResetSet.Contains(i))
9199
{
92100
behaviour.NetworkVariableIndexesToResetSet.Add(i);
93101
behaviour.NetworkVariableIndexesToReset.Add(i);
94102
}
103+
// Set to true for NetworkVariable to ignore duplication of the
104+
// "internal original value" for collections support.
105+
behaviour.NetworkVariableFields[i].NetworkUpdaterCheck = false;
95106
}
96107
}
97108
}
109+
98110
// Now, reset all the no-longer-dirty variables
99111
foreach (var dirtyobj in m_DirtyNetworkObjects)
100112
{
101-
dirtyobj.PostNetworkVariableWrite();
113+
dirtyobj.PostNetworkVariableWrite(forceSend);
114+
// Once done processing, we set the previous owner id to the current owner id
115+
dirtyobj.PreviousOwnerId = dirtyobj.OwnerClientId;
102116
}
103117
m_DirtyNetworkObjects.Clear();
104118
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ private GlobalObjectId GetGlobalId()
272272
/// </summary>
273273
public ulong OwnerClientId { get; internal set; }
274274

275+
internal ulong PreviousOwnerId;
276+
275277
/// <summary>
276278
/// If true, the object will always be replicated as root on clients and the parent will be ignored.
277279
/// </summary>
@@ -1484,6 +1486,14 @@ internal void MarkVariablesDirty(bool dirty)
14841486
}
14851487
}
14861488

1489+
internal void MarkOwnerReadVariablesDirty()
1490+
{
1491+
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
1492+
{
1493+
ChildNetworkBehaviours[i].MarkOwnerReadVariablesDirty();
1494+
}
1495+
}
1496+
14871497
// NGO currently guarantees that the client will receive spawn data for all objects in one network tick.
14881498
// Children may arrive before their parents; when they do they are stored in OrphanedChildren and then
14891499
// resolved when their parents arrived. Because we don't send a partial list of spawns (yet), something
@@ -1725,11 +1735,11 @@ public void Deserialize(FastBufferReader reader)
17251735
}
17261736
}
17271737

1728-
internal void PostNetworkVariableWrite()
1738+
internal void PostNetworkVariableWrite(bool forceSend)
17291739
{
17301740
for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
17311741
{
1732-
ChildNetworkBehaviours[k].PostNetworkVariableWrite();
1742+
ChildNetworkBehaviours[k].PostNetworkVariableWrite(forceSend);
17331743
}
17341744
}
17351745

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,6 @@ public void Handle(ref NetworkContext context)
4545
networkObject.InvokeBehaviourOnLostOwnership();
4646
}
4747

48-
// We are new owner.
49-
if (OwnerClientId == networkManager.LocalClientId)
50-
{
51-
networkObject.InvokeBehaviourOnGainedOwnership();
52-
}
53-
5448
// For all other clients that are neither the former or current owner, update the behaviours' properties
5549
if (OwnerClientId != networkManager.LocalClientId && originalOwner != networkManager.LocalClientId)
5650
{
@@ -60,6 +54,21 @@ public void Handle(ref NetworkContext context)
6054
}
6155
}
6256

57+
// We are new owner.
58+
if (OwnerClientId == networkManager.LocalClientId)
59+
{
60+
networkObject.InvokeBehaviourOnGainedOwnership();
61+
}
62+
63+
if (originalOwner == networkManager.LocalClientId)
64+
{
65+
// Mark any owner read variables as dirty
66+
networkObject.MarkOwnerReadVariablesDirty();
67+
// Immediately queue any pending deltas and order the message before the
68+
// change in ownership message.
69+
networkManager.BehaviourUpdater.NetworkBehaviourUpdate(true);
70+
}
71+
6372
networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId);
6473

6574
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);

0 commit comments

Comments
 (0)