Skip to content

Commit 231b36d

Browse files
fix: collections not detecting deltas backport to v1.x.x (#3005)
* fix Fixing some of the collections issues. Adding a unified permissions error. Replacing throw exceptions with logging errors and returning. * tesat adjustments for replacing throw exception and added internal unified permissions write error. * fix Adjustments made due to other issues with direct assignment. * fix back port typo. * style removing using directive. * fix Mark that we have a previous value for disposing when destroyed. * fix This fixes an issue with duplicating nested lists, hashsets, and dictionaries. * test This is the first set of tests for Lists, including nested, that test using base types and INetworkSerializable. Still have HashSet and Dictionary to cover. * test Added Dictionary and HashSet to the tests. * style Removing whitespaces * update adding changelog entries * update Adding missed change log entries.
1 parent b2f5e84 commit 231b36d

File tree

9 files changed

+3139
-74
lines changed

9 files changed

+3139
-74
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,21 @@ Additional documentation and release notes are available at [Multiplayer Documen
88
[Unreleased]
99
### Added
1010

11+
- Added `NetworkVariable.CheckDirtyState` that is to be used in tandem with collections in order to detect whether the collection or an item within the collection has changed. (#3005)
12+
1113
### Fixed
1214

15+
- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3005)
16+
- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3005)
17+
- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#2999)
18+
- Fixed issue where `FixedStringSerializer<T>` was using `NetworkVariableSerialization<byte>.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#2992)
19+
1320
### Changed
1421

22+
- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)
23+
- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)
24+
25+
1526
## [1.10.0] - 2024-07-22
1627

1728
### Added

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,8 @@ public void Add(T item)
369369
// check write permissions
370370
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
371371
{
372-
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
372+
LogWritePermissionError();
373+
return;
373374
}
374375

375376
m_List.Add(item);
@@ -390,7 +391,8 @@ public void Clear()
390391
// check write permissions
391392
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
392393
{
393-
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
394+
LogWritePermissionError();
395+
return;
394396
}
395397

396398
m_List.Clear();
@@ -416,7 +418,8 @@ public bool Remove(T item)
416418
// check write permissions
417419
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
418420
{
419-
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
421+
LogWritePermissionError();
422+
return false;
420423
}
421424

422425
int index = m_List.IndexOf(item);
@@ -451,7 +454,8 @@ public void Insert(int index, T item)
451454
// check write permissions
452455
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
453456
{
454-
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
457+
LogWritePermissionError();
458+
return;
455459
}
456460

457461
if (index < m_List.Length)
@@ -480,7 +484,8 @@ public void RemoveAt(int index)
480484
// check write permissions
481485
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
482486
{
483-
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
487+
LogWritePermissionError();
488+
return;
484489
}
485490

486491
var value = m_List[index];
@@ -505,7 +510,8 @@ public T this[int index]
505510
// check write permissions
506511
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
507512
{
508-
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
513+
LogWritePermissionError();
514+
return;
509515
}
510516

511517
var previousValue = m_List[index];

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

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,25 +79,57 @@ public NetworkVariable(T value = default,
7979
/// <summary>
8080
/// The value of the NetworkVariable container
8181
/// </summary>
82+
/// <remarks>
83+
/// When assigning collections to <see cref="Value"/>, unless it is a completely new collection this will not
84+
/// detect any deltas with most managed collection classes since assignment of one collection value to another
85+
/// is actually just a reference to the collection itself. <br />
86+
/// To detect deltas in a collection, you should invoke <see cref="CheckDirtyState"/> after making modifications to the collection.
87+
/// </remarks>
8288
public virtual T Value
8389
{
8490
get => m_InternalValue;
8591
set
8692
{
87-
// Compare bitwise
88-
if (NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
93+
if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId))
8994
{
95+
LogWritePermissionError();
9096
return;
9197
}
9298

93-
if (m_NetworkBehaviour && !CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
99+
// Compare the Value being applied to the current value
100+
if (!NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
94101
{
95-
throw new InvalidOperationException("Client is not allowed to write to this NetworkVariable");
102+
T previousValue = m_InternalValue;
103+
m_InternalValue = value;
104+
SetDirty(true);
105+
m_IsDisposed = false;
106+
OnValueChanged?.Invoke(previousValue, m_InternalValue);
96107
}
108+
}
109+
}
110+
111+
/// <summary>
112+
/// Invoke this method to check if a collection's items are dirty.
113+
/// The default behavior is to exit early if the <see cref="NetworkVariable{T}"/> is already dirty.
114+
/// </summary>
115+
/// <param name="forceCheck"> when true, this check will force a full item collection check even if the NetworkVariable is already dirty</param>
116+
/// <remarks>
117+
/// This is to be used as a way to check if a <see cref="NetworkVariable{T}"/> containing a managed collection has any changees to the collection items.<br />
118+
/// If you invoked this when a collection is dirty, it will not trigger the <see cref="OnValueChanged"/> unless you set <param name="forceCheck"/> to true. <br />
119+
/// </remarks>
120+
public bool CheckDirtyState(bool forceCheck = false)
121+
{
122+
var isDirty = base.IsDirty();
97123

98-
Set(value);
124+
// Compare the previous with the current if not dirty or forcing a check.
125+
if ((!isDirty || forceCheck) && !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue))
126+
{
127+
SetDirty(true);
128+
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
99129
m_IsDisposed = false;
130+
isDirty = true;
100131
}
132+
return isDirty;
101133
}
102134

103135
internal ref T RefValue()
@@ -184,9 +216,8 @@ public override void ResetDirty()
184216
private protected void Set(T value)
185217
{
186218
SetDirty(true);
187-
T previousValue = m_InternalValue;
188219
m_InternalValue = value;
189-
OnValueChanged?.Invoke(previousValue, m_InternalValue);
220+
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
190221
}
191222

192223
/// <summary>
@@ -205,20 +236,22 @@ public override void WriteDelta(FastBufferWriter writer)
205236
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
206237
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
207238
{
239+
// In order to get managed collections to properly have a previous and current value, we have to
240+
// duplicate the collection at this point before making any modifications to the current.
241+
m_HasPreviousValue = true;
242+
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
243+
NetworkVariableSerialization<T>.ReadDelta(reader, ref m_InternalValue);
244+
208245
// todo:
209246
// keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients
210247
// In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit
211248
// would be stored in different fields
212-
213-
T previousValue = m_InternalValue;
214-
NetworkVariableSerialization<T>.ReadDelta(reader, ref m_InternalValue);
215-
216249
if (keepDirtyDelta)
217250
{
218251
SetDirty(true);
219252
}
220253

221-
OnValueChanged?.Invoke(previousValue, m_InternalValue);
254+
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
222255
}
223256

224257
/// <inheritdoc />

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,47 @@ public abstract class NetworkVariableBase : IDisposable
3232
/// Maintains a link to the associated NetworkBehaviour
3333
/// </summary>
3434
private protected NetworkBehaviour m_NetworkBehaviour;
35+
private NetworkManager m_InternalNetworkManager;
3536

3637
public NetworkBehaviour GetBehaviour()
3738
{
3839
return m_NetworkBehaviour;
3940
}
4041

42+
internal string GetWritePermissionError()
43+
{
44+
return $"|Client-{m_NetworkManager.LocalClientId}|{m_NetworkBehaviour.name}|{Name}| Write permissions ({WritePerm}) for this client instance is not allowed!";
45+
}
46+
47+
internal void LogWritePermissionError()
48+
{
49+
Debug.LogError(GetWritePermissionError());
50+
}
51+
52+
private protected NetworkManager m_NetworkManager
53+
{
54+
get
55+
{
56+
if (m_InternalNetworkManager == null && m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager)
57+
{
58+
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager;
59+
}
60+
return m_InternalNetworkManager;
61+
}
62+
}
63+
4164
/// <summary>
4265
/// Initializes the NetworkVariable
4366
/// </summary>
4467
/// <param name="networkBehaviour">The NetworkBehaviour the NetworkVariable belongs to</param>
4568
public void Initialize(NetworkBehaviour networkBehaviour)
4669
{
70+
m_InternalNetworkManager = null;
4771
m_NetworkBehaviour = networkBehaviour;
48-
if (m_NetworkBehaviour.NetworkManager)
72+
if (m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager)
4973
{
74+
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager;
75+
5076
if (m_NetworkBehaviour.NetworkManager.NetworkTimeSystem != null)
5177
{
5278
UpdateLastSentTime();

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,10 @@ public void Duplicate(in List<T> value, ref List<T> duplicatedValue)
349349
duplicatedValue.Clear();
350350
foreach (var item in value)
351351
{
352-
duplicatedValue.Add(item);
352+
// This handles the nested list scenario List<List<T>>
353+
T subValue = default;
354+
NetworkVariableSerialization<T>.Duplicate(item, ref subValue);
355+
duplicatedValue.Add(subValue);
353356
}
354357
}
355358
}
@@ -421,6 +424,9 @@ public void Duplicate(in HashSet<T> value, ref HashSet<T> duplicatedValue)
421424
duplicatedValue.Clear();
422425
foreach (var item in value)
423426
{
427+
// Handles nested HashSets
428+
T subValue = default;
429+
NetworkVariableSerialization<T>.Duplicate(item, ref subValue);
424430
duplicatedValue.Add(item);
425431
}
426432
}
@@ -497,7 +503,12 @@ public void Duplicate(in Dictionary<TKey, TVal> value, ref Dictionary<TKey, TVal
497503
duplicatedValue.Clear();
498504
foreach (var item in value)
499505
{
500-
duplicatedValue.Add(item.Key, item.Value);
506+
// Handles nested dictionaries
507+
TKey subKey = default;
508+
TVal subValue = default;
509+
NetworkVariableSerialization<TKey>.Duplicate(item.Key, ref subKey);
510+
NetworkVariableSerialization<TVal>.Duplicate(item.Value, ref subValue);
511+
duplicatedValue.Add(subKey, subValue);
501512
}
502513
}
503514
}

0 commit comments

Comments
 (0)