Skip to content

Commit 9b0dc26

Browse files
authored
Merge branch 'develop-2.0.0' into fix/bitwriter-grow-keeps-bitwise-bytes
2 parents 991180e + 7951347 commit 9b0dc26

6 files changed

Lines changed: 241 additions & 10 deletions

File tree

‎com.unity.netcode.gameobjects/CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ Additional documentation and release notes are available at [Multiplayer Documen
2323
### Fixed
2424

2525
- Issue where bits written with `BitWriter` were lost when a later `BitWriter.TryBeginWriteBits` call in the same bitwise context grew the `FastBufferWriter`. The bytes written earlier in that context were replaced with zeros or uninitialized memory. (#4225)
26+
- Issue where a `FastBufferReader` created with an offset and the default length of -1 read past the end of its source. The `ArraySegment<byte>` overload that takes an offset now applies it from the start of the segment. (#4221)
27+
- Issue where a client synchronized while a `NetworkList<T>` still had unsent changes applied those changes twice, resulting in duplicate entries and index-based events landing on the wrong elements on that client. (#4215)
28+
- Issue where showing a `NetworkObject` with `NetworkShow` while its `NetworkVariable` updates were held back by `NetworkVariableUpdateTraits` stopped those pending changes from being sent until the variable was modified again. (#4215)
2629
- Issue where moving a `NetworkObject` into another scene made the clients that did not observe it log "Trying to synchronize NetworkObjectId but it was not spawned". The scene migration is now only sent to the clients that observe the `NetworkObject`. (#4185)
2730
- Issue where a `NetworkObject` that was moved into another scene while hidden from a client spawned in that client's active scene when it was shown with `NetworkShow`, instead of the scene it is in on the server. (#4185)
2831
- Issue where mixed authority nested `NetworkTransform` instances could stop child/nested instances from updating because an authoritative `NetworkTransform` (root or otherwise) would remove the `NetworkObject` from the update group, preventing non-authoritative instances from updating their state on the authority side. (#4169)

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,19 @@ internal void ForceSendIfDirtyOnNetworkShow(NetworkObject networkObject)
138138
// Remove it from the pending and queued dirty objects lists
139139
m_PendingDirtyNetworkObjects.Remove(networkObject);
140140
m_DirtyNetworkObjects.Remove(networkObject);
141+
142+
// Anything still dirty was held back by its update traits, keep it queued so a later tick sends it
143+
foreach (var behaviour in networkObject.ChildNetworkBehaviours.Values)
144+
{
145+
foreach (var networkVariable in behaviour.NetworkVariableFields)
146+
{
147+
if (networkVariable.IsDirty())
148+
{
149+
AddForUpdate(networkObject);
150+
return;
151+
}
152+
}
153+
}
141154
}
142155

143156
/// <summary>

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

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatabl
1616
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
1717
private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
1818

19+
// The list as it was before the first local change that is still pending in m_DirtyEvents
20+
private NativeList<T> m_LastSentList = new NativeList<T>(64, Allocator.Persistent);
21+
private bool m_HasLastSentList;
22+
1923
/// <summary>
2024
/// Delegate type for list changed event
2125
/// </summary>
@@ -67,6 +71,7 @@ public override void ResetDirty()
6771
{
6872
m_DirtyEvents.Clear();
6973
}
74+
m_HasLastSentList = false;
7075
}
7176

7277
/// <inheritdoc cref="NetworkVariable{T}.IsDirty"/>
@@ -149,10 +154,42 @@ private T ReadValue(FastBufferReader reader)
149154
/// <inheritdoc cref="NetworkVariable{T}.WriteField"/>
150155
public override void WriteField(FastBufferWriter writer)
151156
{
152-
writer.WriteValueSafe((ushort)m_List.Length);
153-
for (int i = 0; i < m_List.Length; i++)
157+
WriteList(writer, m_List);
158+
}
159+
160+
internal override void WriteFieldSynchronization(FastBufferWriter writer)
161+
{
162+
// The pending events are sent to the synchronizing client with the next delta, so it must
163+
// receive the list they apply to. Otherwise it would apply them twice.
164+
if (m_HasLastSentList && m_DirtyEvents.Length > 0)
165+
{
166+
WriteList(writer, m_LastSentList);
167+
}
168+
else
169+
{
170+
base.WriteFieldSynchronization(writer);
171+
}
172+
}
173+
174+
private static void WriteList(FastBufferWriter writer, NativeList<T> list)
175+
{
176+
writer.WriteValueSafe((ushort)list.Length);
177+
for (int i = 0; i < list.Length; i++)
154178
{
155-
NetworkVariableSerialization<T>.Serializer.Write(writer, ref m_List.ElementAt(i));
179+
NetworkVariableSerialization<T>.Serializer.Write(writer, ref list.ElementAt(i));
180+
}
181+
}
182+
183+
/// <summary>
184+
/// Invoked before a local change. Keeps a copy of the list as of the last sent delta
185+
/// so <see cref="WriteFieldSynchronization"/> can send it while changes are pending.
186+
/// </summary>
187+
private void CaptureLastSentList()
188+
{
189+
if (m_DirtyEvents.Length == 0)
190+
{
191+
m_LastSentList.CopyFrom(m_List);
192+
m_HasLastSentList = true;
156193
}
157194
}
158195

@@ -439,6 +476,7 @@ public void Add(T item)
439476
return;
440477
}
441478

479+
CaptureLastSentList();
442480
m_List.Add(item);
443481

444482
var listEvent = new NetworkListEvent<T>()
@@ -466,6 +504,7 @@ public void Clear()
466504
return;
467505
}
468506

507+
CaptureLastSentList();
469508
m_List.Clear();
470509

471510
var listEvent = new NetworkListEvent<T>()
@@ -510,6 +549,7 @@ public bool Remove(T item)
510549
return false;
511550
}
512551

552+
CaptureLastSentList();
513553
m_List.RemoveAt(index);
514554
var listEvent = new NetworkListEvent<T>()
515555
{
@@ -553,6 +593,7 @@ public void Insert(int index, T item)
553593
return;
554594
}
555595

596+
CaptureLastSentList();
556597
if (index < m_List.Length)
557598
{
558599
m_List.InsertRangeWithBeginEnd(index, index + 1);
@@ -589,6 +630,7 @@ public void RemoveAt(int index)
589630
}
590631

591632
var value = m_List[index];
633+
CaptureLastSentList();
592634
m_List.RemoveAt(index);
593635

594636
var listEvent = new NetworkListEvent<T>()
@@ -631,6 +673,7 @@ public void Set(int index, T value, bool forceUpdate = false)
631673
return;
632674
}
633675

676+
CaptureLastSentList();
634677
m_List[index] = value;
635678

636679
var listEvent = new NetworkListEvent<T>()
@@ -706,6 +749,11 @@ public override void Dispose()
706749
m_DirtyEvents.Dispose();
707750
}
708751

752+
if (m_LastSentList.IsCreated)
753+
{
754+
m_LastSentList.Dispose();
755+
}
756+
709757
base.Dispose();
710758
}
711759
}

‎com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ internal unsafe void CommitBitwiseReads(int amount)
9090
return readerHandle;
9191
}
9292

93+
// A copy starts at offset, so -1 copies what follows it. Allocator.None keeps the whole source and starts reading at offset.
94+
private static int ResolveLength(int length, int sourceLength, int offset, Allocator copyAllocator)
95+
{
96+
if (length != -1)
97+
{
98+
return length;
99+
}
100+
return copyAllocator == Allocator.None ? sourceLength : sourceLength - offset;
101+
}
102+
93103
/// <summary>
94104
/// Create a FastBufferReader from a NativeArray.
95105
///
@@ -107,12 +117,12 @@ internal unsafe void CommitBitwiseReads(int amount)
107117
/// </summary>
108118
/// <param name="buffer">The NativeArray to create the reader from</param>
109119
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
110-
/// <param name="length">The number of bytes to read from the buffer. If set to -1, the entire buffer length will be used</param>
120+
/// <param name="length">The number of bytes to read from the buffer. If set to -1, every byte from offset to the end of the buffer will be used</param>
111121
/// <param name="offset">The offset into the buffer to start reading from</param>
112122
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
113123
public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
114124
{
115-
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, copyAllocator, internalAllocator);
125+
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), ResolveLength(length, buffer.Length, offset, copyAllocator), offset, copyAllocator, internalAllocator);
116126
}
117127

118128
/// <summary>
@@ -127,7 +137,7 @@ public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator copyAllocator
127137
/// <param name="buffer">The buffer to copy from</param>
128138
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
129139
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
130-
/// <param name="offset">The offset of the buffer to start copying from</param>
140+
/// <param name="offset">The offset into the segment to start copying from</param>
131141
public unsafe FastBufferReader(ArraySegment<byte> buffer, Allocator copyAllocator, int length = -1, int offset = 0)
132142
{
133143
if (copyAllocator == Allocator.None)
@@ -136,7 +146,7 @@ public unsafe FastBufferReader(ArraySegment<byte> buffer, Allocator copyAllocato
136146
}
137147
fixed (byte* data = buffer.Array)
138148
{
139-
Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, copyAllocator, Allocator.Temp);
149+
Handle = CreateHandle(data + buffer.Offset, ResolveLength(length, buffer.Count, offset, copyAllocator), offset, copyAllocator, Allocator.Temp);
140150
}
141151
}
142152

@@ -209,7 +219,7 @@ public unsafe FastBufferReader(byte[] buffer, Allocator copyAllocator, int lengt
209219
}
210220
fixed (byte* data = buffer)
211221
{
212-
Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, copyAllocator, Allocator.Temp);
222+
Handle = CreateHandle(data, ResolveLength(length, buffer.Length, offset, copyAllocator), offset, copyAllocator, Allocator.Temp);
213223
}
214224
}
215225

@@ -260,7 +270,7 @@ public unsafe FastBufferReader(byte* buffer, Allocator copyAllocator, int length
260270
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
261271
public unsafe FastBufferReader(FastBufferWriter writer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
262272
{
263-
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, copyAllocator, internalAllocator);
273+
Handle = CreateHandle(writer.GetUnsafePtr(), ResolveLength(length, writer.Length, offset, copyAllocator), offset, copyAllocator, internalAllocator);
264274
}
265275

266276
/// <summary>
@@ -285,7 +295,7 @@ public unsafe FastBufferReader(FastBufferWriter writer, Allocator copyAllocator,
285295
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
286296
public unsafe FastBufferReader(FastBufferReader reader, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
287297
{
288-
Handle = CreateHandle(reader.GetUnsafePtr(), length == -1 ? reader.Length : length, offset, copyAllocator, internalAllocator);
298+
Handle = CreateHandle(reader.GetUnsafePtr(), ResolveLength(length, reader.Length, offset, copyAllocator), offset, copyAllocator, internalAllocator);
289299
}
290300

291301
/// <summary>

‎com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,78 @@ public void WhenCreatingAReaderFromAnEmptyBuffer_LengthIsZero()
11181118
}
11191119
}
11201120

1121+
private const int k_SourceLength = 10;
1122+
private const int k_ReaderOffset = 2;
1123+
1124+
private static byte[] CreateSequentialBytes()
1125+
{
1126+
var bytes = new byte[k_SourceLength];
1127+
for (var i = 0; i < bytes.Length; ++i)
1128+
{
1129+
bytes[i] = (byte)i;
1130+
}
1131+
return bytes;
1132+
}
1133+
1134+
private static void AssertReadsToEndFrom(FastBufferReader reader, int firstValue)
1135+
{
1136+
var expectedCount = k_SourceLength - firstValue;
1137+
Assert.AreEqual(expectedCount, reader.Length - reader.Position);
1138+
for (var i = 0; i < expectedCount; ++i)
1139+
{
1140+
reader.ReadByteSafe(out byte value);
1141+
Assert.AreEqual((byte)(firstValue + i), value);
1142+
}
1143+
}
1144+
1145+
[Test]
1146+
public void WhenCreatingAReaderFromAnArrayWithAnOffsetAndNoLength_TheRestOfTheArrayIsCopied()
1147+
{
1148+
using var reader = new FastBufferReader(CreateSequentialBytes(), Allocator.Temp, -1, k_ReaderOffset);
1149+
AssertReadsToEndFrom(reader, k_ReaderOffset);
1150+
}
1151+
1152+
[Test]
1153+
public void WhenCreatingAReaderFromAnArraySegmentWithAnOffsetAndNoLength_TheRestOfTheSegmentIsCopied()
1154+
{
1155+
using var reader = new FastBufferReader(new ArraySegment<byte>(CreateSequentialBytes()), Allocator.Temp, -1, k_ReaderOffset);
1156+
AssertReadsToEndFrom(reader, k_ReaderOffset);
1157+
}
1158+
1159+
[Test]
1160+
public void WhenCreatingAReaderFromAnOffsetArraySegmentWithAnOffset_TheOffsetIsRelativeToTheSegment()
1161+
{
1162+
var segmentOffset = 3;
1163+
var segment = new ArraySegment<byte>(CreateSequentialBytes(), segmentOffset, k_SourceLength - segmentOffset);
1164+
using var reader = new FastBufferReader(segment, Allocator.Temp, -1, k_ReaderOffset);
1165+
AssertReadsToEndFrom(reader, segmentOffset + k_ReaderOffset);
1166+
}
1167+
1168+
[Test]
1169+
public void WhenCreatingAReaderFromANativeArrayWithAnOffsetAndNoLength_TheRestOfTheArrayIsRead([Values(Allocator.Temp, Allocator.None)] Allocator copyAllocator)
1170+
{
1171+
using var source = new NativeArray<byte>(CreateSequentialBytes(), Allocator.Temp);
1172+
using var reader = new FastBufferReader(source, copyAllocator, -1, k_ReaderOffset);
1173+
AssertReadsToEndFrom(reader, k_ReaderOffset);
1174+
}
1175+
1176+
[Test]
1177+
public void WhenCreatingAReaderFromAWriterWithAnOffsetAndNoLength_TheRestOfTheWriterIsRead([Values(Allocator.Temp, Allocator.None)] Allocator copyAllocator)
1178+
{
1179+
using var writer = new FastBufferWriter(k_SourceLength, Allocator.Temp);
1180+
writer.WriteBytesSafe(CreateSequentialBytes());
1181+
using var reader = new FastBufferReader(writer, copyAllocator, -1, k_ReaderOffset);
1182+
AssertReadsToEndFrom(reader, k_ReaderOffset);
1183+
}
1184+
1185+
[Test]
1186+
public void WhenCreatingAReaderFromAReaderWithAnOffsetAndNoLength_TheRestOfTheReaderIsRead([Values(Allocator.Temp, Allocator.None)] Allocator copyAllocator)
1187+
{
1188+
using var source = new FastBufferReader(CreateSequentialBytes(), Allocator.Temp);
1189+
using var reader = new FastBufferReader(source, copyAllocator, -1, k_ReaderOffset);
1190+
AssertReadsToEndFrom(reader, k_ReaderOffset);
1191+
}
1192+
11211193
[Test]
11221194
public void WhenCreatingNewFastBufferReader_IsInitializedIsTrue()
11231195
{

0 commit comments

Comments
 (0)