Skip to content

Commit a93c4d8

Browse files
authored
fix: [Backport] collection authority OnValueChanged (#3544)
https://jira.unity3d.com/browse/MTTB-1421 fixes: #3534 ## Changelog - Fixed: When calling `CheckDirtyState(true)` on a `NetworkVariable` collection, `OnValueChanged` now triggers on the authority if the authority has changed the collection and then changed it back to the initial state in the same frame. This matches the behaviour of non-collection NetworkVariables. ## Testing and Documentation - Includes unit tests. - Includes integration tests. ## Backport This is a backport of #3539
1 parent 3da80e7 commit a93c4d8

File tree

7 files changed

+796
-517
lines changed

7 files changed

+796
-517
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

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

1818
### Fixed
1919

20+
- Fixed ensuring OnValueChanged callback is still triggered on the authority when a collection changes and then reverts to the previous value in the same frame. (#3544)
2021
- Fixed `NullReferenceException` on `NetworkList` when used without a NetworkManager in scene. (#3502)
2122
- Fixed inconsistencies in the `OnSceneEvent` callback. (#3487)
2223
- Fixed issue where `NetworkClient` could persist some settings if re-using the same `NetworkManager` instance. (#3494)

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

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public override void OnInitialize()
5757
base.OnInitialize();
5858

5959
m_HasPreviousValue = true;
60-
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
60+
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_LastInternalValue);
6161
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
6262
}
6363

@@ -73,7 +73,7 @@ public NetworkVariable(T value = default,
7373
: base(readPerm, writePerm)
7474
{
7575
m_InternalValue = value;
76-
m_InternalOriginalValue = default;
76+
m_LastInternalValue = default;
7777
// Since we start with IsDirty = true, this doesn't need to be duplicated
7878
// right away. It won't get read until after ResetDirty() is called, and
7979
// the duplicate will be made there. Avoiding calling
@@ -92,25 +92,45 @@ public void Reset(T value = default)
9292
if (m_NetworkBehaviour == null || m_NetworkBehaviour != null && !m_NetworkBehaviour.NetworkObject.IsSpawned)
9393
{
9494
m_InternalValue = value;
95-
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
95+
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_LastInternalValue);
9696
m_PreviousValue = default;
9797
}
9898
}
9999

100100
/// <summary>
101-
/// The internal value of the NetworkVariable
101+
/// The current internal value of the NetworkVariable.
102102
/// </summary>
103+
/// <remarks>
104+
/// When using collections, this InternalValue can be updated directly without going through the <see cref="NetworkVariable{T}.Value"/> setter.
105+
/// </remarks>
103106
[SerializeField]
104107
private protected T m_InternalValue;
105108

106-
// The introduction of standard .NET collections caused an issue with permissions since there is no way to detect changes in the
107-
// collection without doing a full comparison. While this approach does consume more memory per collection instance, it is the
108-
// lowest risk approach to resolving the issue where a client with no write permissions could make changes to a collection locally
109-
// which can cause a myriad of issues.
110-
private protected T m_InternalOriginalValue;
109+
/// <summary>
110+
/// The last valid/authorized value of the network variable.
111+
/// </summary>
112+
/// <remarks>
113+
/// The introduction of standard .NET collections caused an issue with permissions since there is no way to detect changes in the
114+
/// collection without doing a full comparison. While this approach does consume more memory per collection instance, it is the
115+
/// lowest risk approach to resolving the issue where a client with no write permissions could make changes to a collection locally
116+
/// which can cause a myriad of issues.
117+
/// </remarks>
118+
private protected T m_LastInternalValue;
111119

120+
/// <summary>
121+
/// The most recent value that was synchronized over the network.
122+
/// Synchronized over the network at the end of the frame in which the <see cref="NetworkVariable{T}"/> was marked dirty.
123+
/// </summary>
124+
/// <remarks>
125+
/// Only contains the value synchronized over the network at the end of the last frame.
126+
/// All in-between changes on the authority are tracked by <see cref="m_LastInternalValue"/>.
127+
/// </remarks>
112128
private protected T m_PreviousValue;
113129

130+
/// <summary>
131+
/// Whether this network variable has had changes synchronized over the network.
132+
/// Indicates whether <see cref="m_PreviousValue"/> is populated and valid.
133+
/// </summary>
114134
private bool m_HasPreviousValue;
115135
private bool m_IsDisposed;
116136

@@ -139,7 +159,7 @@ public virtual T Value
139159
{
140160
T previousValue = m_InternalValue;
141161
m_InternalValue = value;
142-
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
162+
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_LastInternalValue);
143163
SetDirty(true);
144164
m_IsDisposed = false;
145165
OnValueChanged?.Invoke(previousValue, m_InternalValue);
@@ -165,20 +185,21 @@ public bool CheckDirtyState(bool forceCheck = false)
165185
if (CannotWrite)
166186
{
167187
// If modifications are detected, then revert back to the last known current value
168-
if (!NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_InternalOriginalValue))
188+
if (!NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_LastInternalValue))
169189
{
170-
NetworkVariableSerialization<T>.Duplicate(m_InternalOriginalValue, ref m_InternalValue);
190+
NetworkVariableSerialization<T>.Duplicate(m_LastInternalValue, ref m_InternalValue);
171191
}
172192
return false;
173193
}
174194

175-
// Compare the previous with the current if not dirty or forcing a check.
176-
if ((!isDirty || forceCheck) && !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue))
195+
// Compare the last internal value with the current value if not dirty or forcing a check.
196+
if ((!isDirty || forceCheck) && !NetworkVariableSerialization<T>.AreEqual(ref m_LastInternalValue, ref m_InternalValue))
177197
{
178198
SetDirty(true);
179-
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
199+
OnValueChanged?.Invoke(m_LastInternalValue, m_InternalValue);
180200
m_IsDisposed = false;
181201
isDirty = true;
202+
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_LastInternalValue);
182203
}
183204
return isDirty;
184205
}
@@ -211,11 +232,11 @@ public override void Dispose()
211232

212233
m_InternalValue = default;
213234

214-
if (m_InternalOriginalValue is IDisposable internalOriginalValueDisposable)
235+
if (m_LastInternalValue is IDisposable internalOriginalValueDisposable)
215236
{
216237
internalOriginalValueDisposable.Dispose();
217238
}
218-
m_InternalOriginalValue = default;
239+
m_LastInternalValue = default;
219240

220241
if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable)
221242
{
@@ -242,9 +263,9 @@ public override bool IsDirty()
242263
{
243264
// If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert
244265
// to the original collection value prior to applying updates (primarily for collections).
245-
if (!NetworkUpdaterCheck && CannotWrite && !NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_InternalOriginalValue))
266+
if (!NetworkUpdaterCheck && CannotWrite && !NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_LastInternalValue))
246267
{
247-
NetworkVariableSerialization<T>.Duplicate(m_InternalOriginalValue, ref m_InternalValue);
268+
NetworkVariableSerialization<T>.Duplicate(m_LastInternalValue, ref m_InternalValue);
248269
return true;
249270
}
250271

@@ -283,7 +304,7 @@ public override void ResetDirty()
283304
m_HasPreviousValue = true;
284305
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
285306
// Once updated, assure the original current value is updated for future comparison purposes
286-
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
307+
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_LastInternalValue);
287308
}
288309
base.ResetDirty();
289310
}
@@ -318,9 +339,9 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
318339
{
319340
// If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert
320341
// to the original collection value prior to applying updates (primarily for collections).
321-
if (CannotWrite && !NetworkVariableSerialization<T>.AreEqual(ref m_InternalOriginalValue, ref m_InternalValue))
342+
if (CannotWrite && !NetworkVariableSerialization<T>.AreEqual(ref m_LastInternalValue, ref m_InternalValue))
322343
{
323-
NetworkVariableSerialization<T>.Duplicate(m_InternalOriginalValue, ref m_InternalValue);
344+
NetworkVariableSerialization<T>.Duplicate(m_LastInternalValue, ref m_InternalValue);
324345
}
325346

326347
NetworkVariableSerialization<T>.ReadDelta(reader, ref m_InternalValue);
@@ -351,17 +372,17 @@ internal override void PostDeltaRead()
351372
m_HasPreviousValue = true;
352373
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
353374
// Once updated, assure the original current value is updated for future comparison purposes
354-
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
375+
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_LastInternalValue);
355376
}
356377

357378
/// <inheritdoc />
358379
public override void ReadField(FastBufferReader reader)
359380
{
360381
// If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert
361382
// to the original collection value prior to applying updates (primarily for collections).
362-
if (CannotWrite && !NetworkVariableSerialization<T>.AreEqual(ref m_InternalOriginalValue, ref m_InternalValue))
383+
if (CannotWrite && !NetworkVariableSerialization<T>.AreEqual(ref m_LastInternalValue, ref m_InternalValue))
363384
{
364-
NetworkVariableSerialization<T>.Duplicate(m_InternalOriginalValue, ref m_InternalValue);
385+
NetworkVariableSerialization<T>.Duplicate(m_LastInternalValue, ref m_InternalValue);
365386
}
366387

367388
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
@@ -373,7 +394,7 @@ public override void ReadField(FastBufferReader reader)
373394
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
374395

375396
// Once updated, assure the original current value is updated for future comparison purposes
376-
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
397+
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_LastInternalValue);
377398
}
378399

379400
/// <inheritdoc />

0 commit comments

Comments
 (0)