Skip to content

Commit 6e322fa

Browse files
author
Unity Technologies
committed
com.unity.netcode@1.0.0-pre.47
## [1.0.0-pre.47] - 2023-02-28 ### Fixed * Snapshot history buffer not restore correctly, causing entities component to be stomped with random data.
1 parent d5cd132 commit 6e322fa

File tree

5 files changed

+309
-86
lines changed

5 files changed

+309
-86
lines changed

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [1.0.0-pre.47] - 2023-02-28
4+
5+
### Fixed
6+
7+
* Snapshot history buffer not restore correctly, causing entities component to be stomped with random data.
8+
9+
310
## [1.0.0-pre.44] - 2023-02-13
411

512
### Added
@@ -97,7 +104,7 @@
97104
* `GhostUpdateSystem` now supports Change Filtering, so components on the client will now only be marked as changed _when they actually are changed_. We strongly recommend implementing change filtering when reading components containing `[GhostField]`s and `[GhostEnabledBit]`s on the client.
98105
* Fixed input component codegen issue when the type is nested in a parent class
99106
* Exposed NetworkTick value to Entity Inspector.
100-
* Fixed code-gen error where `ICommandData.Tick` was not being replicated.
107+
* Fixed code-gen error where `ICommandData.Tick` was not being replicated.
101108
* Fixed code-gen GhostField error handling when dealing with properties on Buffers, Commands, and Components.
102109
* Fixed code-gen exceptions for `Entity`s, `float`s, `double`s, `quaternions` and `ulong`s in specific conditions (unquantized, or in commands). Also improved exception reporting when trying to set an invalid `SmoothingAction` on `ICommandData`s.
103110
* Code-gen now will not explode if you have very long field names (support upto 509 characters, from 61), and will not throw truncation errors if you have too many fields.
@@ -106,7 +113,7 @@
106113
* If there are failed writes in the ICommandData batched send.
107114
* ICommandData batches now support fragmentation, which means writing multiple ICommandData's will no longer silently fail to send.
108115
* ICommandData now properly supports `floats`, `doubles`, `ulong`, and `Entity` types.
109-
* Fixed various Variant selection issues. In particular, `PrefabType` rules defined in `GhostComponentAttribute` of the "Default Serializer" will now be propagated to all of its `DontSerializeVariant`s.
116+
* Fixed various Variant selection issues. In particular, `PrefabType` rules defined in `GhostComponentAttribute` of the "Default Serializer" will now be propagated to all of its `DontSerializeVariant`s.
110117
* Optimized string locale.
111118
* Netcode settings assets could be modified and saved when asset modification was not allowed.
112119

Runtime/Snapshot/GhostPredictionHistorySystem.cs

Lines changed: 79 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static IntPtr AllocNew(int ghostTypeId, int enabledBits, int dataSize, in
5454
state->dataOffset = headerSize + entitiesSize + enabledBitSize;
5555
state->dataSize = dataSize;
5656
state->bufferDataCapacity = buffersDataCapacity;
57-
state->bufferDataOffset = headerSize + entitiesSize + dataSize;
57+
state->bufferDataOffset = state->dataOffset + dataSize;
5858
return (IntPtr)state;
5959
}
6060
public static int GetHeaderSize()
@@ -462,6 +462,12 @@ public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useE
462462
if ((GhostComponentIndex[baseOffset + comp].SendMask&requiredSendMask) == 0)
463463
continue;
464464

465+
if (GhostComponentCollection[serializerIdx].SerializesEnabledBit != 0)
466+
++enabledBits;
467+
468+
if (!GhostComponentCollection[serializerIdx].HasGhostFields)
469+
continue;
470+
465471
if (GhostComponentCollection[serializerIdx].ComponentType.TypeIndex == ghostOwnerTypeIndex)
466472
predictionOwnerOffset = dataSize;
467473

@@ -473,8 +479,6 @@ public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useE
473479
GhostComponentCollection[serializerIdx].ComponentSize, chunk.Capacity);
474480
else
475481
dataSize += PredictionBackupState.GetDataSize(GhostSystemConstants.DynamicBufferComponentSnapshotSize, chunk.Capacity);
476-
if (GhostComponentCollection[serializerIdx].SerializesEnabledBit != 0)
477-
++enabledBits;
478482
}
479483

480484
//compute the space necessary to store the dynamic buffers data for the chunk
@@ -545,41 +549,40 @@ public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useE
545549
// Note that `HasGhostFields` reads the `SnapshotSize` of this type, BUT we're saving the entire component.
546550
// The reason we use this is: Why bother memcopy-ing the entire component state, if we're never actually going to be writing any data back?
547551
// I.e. Only the GhostFields will be written back anyway.
548-
if (GhostComponentCollection[serializerIdx].HasGhostFields)
552+
if (!GhostComponentCollection[serializerIdx].HasGhostFields)
553+
continue;
554+
555+
if (!chunk.Has(ref ghostChunkComponentTypesPtr[compIdx]))
549556
{
550-
if (!chunk.Has(ref ghostChunkComponentTypesPtr[compIdx]))
551-
{
552-
UnsafeUtility.MemClear(dataPtr, chunk.Count * compSize);
553-
}
554-
else if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer)
557+
UnsafeUtility.MemClear(dataPtr, chunk.Count * compSize);
558+
}
559+
else if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer)
560+
{
561+
var compData = (byte*) chunk.GetDynamicComponentDataArrayReinterpret<byte>(ref ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr();
562+
UnsafeUtility.MemCpy(dataPtr, compData, chunk.Count * compSize);
563+
}
564+
else
565+
{
566+
var bufferData = chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]);
567+
var bufElemSize = GhostComponentCollection[serializerIdx].ComponentSize;
568+
//Use local variable to iterate and set the buffer offset and length. The dataptr must be
569+
//advanced "per chunk" to the next correct position
570+
var tempDataPtr = dataPtr;
571+
for (int i = 0; i < bufferData.Length; ++i)
555572
{
556-
var compData = (byte*) chunk.GetDynamicComponentDataArrayReinterpret<byte>(ref ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr();
557-
UnsafeUtility.MemCpy(dataPtr, compData, chunk.Count * compSize);
573+
//Retrieve an copy each buffer data. Set size and offset in the backup buffer in the component backup
574+
var bufferPtr = bufferData.GetUnsafeReadOnlyPtrAndLength(i, out var size);
575+
((int*) tempDataPtr)[0] = size;
576+
((int*) tempDataPtr)[1] = bufferBackupDataOffset;
577+
if (size > 0)
578+
UnsafeUtility.MemCpy(bufferBackupDataPtr + bufferBackupDataOffset, (byte*) bufferPtr, size * bufElemSize);
579+
bufferBackupDataOffset += size * bufElemSize;
580+
tempDataPtr += compSize;
558581
}
559-
else
560-
{
561-
var bufferData = chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]);
562-
var bufElemSize = GhostComponentCollection[serializerIdx].ComponentSize;
563-
//Use local variable to iterate and set the buffer offset and length. The dataptr must be
564-
//advanced "per chunk" to the next correct position
565-
var tempDataPtr = dataPtr;
566-
for (int i = 0; i < bufferData.Length; ++i)
567-
{
568-
//Retrieve an copy each buffer data. Set size and offset in the backup buffer in the component backup
569-
var bufferPtr = bufferData.GetUnsafeReadOnlyPtrAndLength(i, out var size);
570-
((int*) tempDataPtr)[0] = size;
571-
((int*) tempDataPtr)[1] = bufferBackupDataOffset;
572-
if (size > 0)
573-
UnsafeUtility.MemCpy(bufferBackupDataPtr + bufferBackupDataOffset, (byte*) bufferPtr, size * bufElemSize);
574-
bufferBackupDataOffset += size * bufElemSize;
575-
tempDataPtr += compSize;
576-
}
577582

578-
bufferBackupDataOffset = GhostComponentSerializer.SnapshotSizeAligned(bufferBackupDataOffset);
579-
}
580-
581-
dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity);
583+
bufferBackupDataOffset = GhostComponentSerializer.SnapshotSizeAligned(bufferBackupDataOffset);
582584
}
585+
dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity);
583586
}
584587
if (typeData.NumChildComponents > 0)
585588
{
@@ -617,61 +620,61 @@ public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useE
617620
var isBuffer = GhostComponentCollection[serializerIdx].ComponentType.IsBuffer;
618621
var compSize = isBuffer ? GhostSystemConstants.DynamicBufferComponentSnapshotSize : GhostComponentCollection[serializerIdx].ComponentSize;
619622

620-
if (GhostComponentCollection[serializerIdx].HasGhostFields)
623+
if (!GhostComponentCollection[serializerIdx].HasGhostFields)
624+
continue;
625+
626+
if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer)
621627
{
622-
if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer)
628+
//use a temporary for the iteration here. Otherwise when the dataptr is offset for the chunk, we
629+
//end up in the wrong position
630+
var tempDataPtr = dataPtr;
631+
for (int ent = 0, chunkEntityCount = chunk.Count; ent < chunkEntityCount; ++ent)
623632
{
624-
//use a temporary for the iteration here. Otherwise when the dataptr is offset for the chunk, we
625-
//end up in the wrong position
626-
var tempDataPtr = dataPtr;
627-
for (int ent = 0, chunkEntityCount = chunk.Count; ent < chunkEntityCount; ++ent)
633+
var linkedEntityGroup = linkedEntityGroupAccessor[ent];
634+
var childEnt = linkedEntityGroup[GhostComponentIndex[baseOffset + comp].EntityIndex].Value;
635+
if (childEntityLookup.TryGetValue(childEnt, out var childChunk) && childChunk.Chunk.Has(ref ghostChunkComponentTypesPtr[compIdx]))
628636
{
629-
var linkedEntityGroup = linkedEntityGroupAccessor[ent];
630-
var childEnt = linkedEntityGroup[GhostComponentIndex[baseOffset + comp].EntityIndex].Value;
631-
if (childEntityLookup.TryGetValue(childEnt, out var childChunk) && childChunk.Chunk.Has(ref ghostChunkComponentTypesPtr[compIdx]))
632-
{
633-
var compData = (byte*) childChunk.Chunk.GetDynamicComponentDataArrayReinterpret<byte>(ref ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr();
634-
UnsafeUtility.MemCpy(tempDataPtr, compData + childChunk.IndexInChunk * compSize, compSize);
635-
}
636-
else
637-
UnsafeUtility.MemClear(tempDataPtr, compSize);
638-
639-
tempDataPtr += compSize;
637+
var compData = (byte*) childChunk.Chunk.GetDynamicComponentDataArrayReinterpret<byte>(ref ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr();
638+
UnsafeUtility.MemCpy(tempDataPtr, compData + childChunk.IndexInChunk * compSize, compSize);
640639
}
640+
else
641+
UnsafeUtility.MemClear(tempDataPtr, compSize);
642+
643+
tempDataPtr += compSize;
641644
}
642-
else
645+
}
646+
else
647+
{
648+
var bufElemSize = GhostComponentCollection[serializerIdx].ComponentSize;
649+
var tempDataPtr = dataPtr;
650+
for (int ent = 0, chunkEntityCount = chunk.Count; ent < chunkEntityCount; ++ent)
643651
{
644-
var bufElemSize = GhostComponentCollection[serializerIdx].ComponentSize;
645-
var tempDataPtr = dataPtr;
646-
for (int ent = 0, chunkEntityCount = chunk.Count; ent < chunkEntityCount; ++ent)
652+
var linkedEntityGroup = linkedEntityGroupAccessor[ent];
653+
var childEnt = linkedEntityGroup[GhostComponentIndex[baseOffset + comp].EntityIndex].Value;
654+
if (childEntityLookup.TryGetValue(childEnt, out var childChunk) && childChunk.Chunk.Has(ref ghostChunkComponentTypesPtr[compIdx]))
647655
{
648-
var linkedEntityGroup = linkedEntityGroupAccessor[ent];
649-
var childEnt = linkedEntityGroup[GhostComponentIndex[baseOffset + comp].EntityIndex].Value;
650-
if (childEntityLookup.TryGetValue(childEnt, out var childChunk) && childChunk.Chunk.Has(ref ghostChunkComponentTypesPtr[compIdx]))
651-
{
652-
var bufferData = childChunk.Chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]);
653-
//Retrieve an copy each buffer data. Set size and offset in the backup buffer in the component backup
654-
var bufferPtr = bufferData.GetUnsafeReadOnlyPtrAndLength(childChunk.IndexInChunk, out var size);
655-
((int*) tempDataPtr)[0] = size;
656-
((int*) tempDataPtr)[1] = bufferBackupDataOffset;
657-
if (size > 0)
658-
UnsafeUtility.MemCpy(bufferBackupDataPtr + bufferBackupDataOffset, (byte*) bufferPtr, size * bufElemSize);
659-
bufferBackupDataOffset += size * bufElemSize;
660-
}
661-
else
662-
{
663-
//reset the entry to 0. Don't use memcpy in this case (is faster this way)
664-
((long*) tempDataPtr)[0] = 0;
665-
}
666-
667-
tempDataPtr += compSize;
656+
var bufferData = childChunk.Chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]);
657+
//Retrieve an copy each buffer data. Set size and offset in the backup buffer in the component backup
658+
var bufferPtr = bufferData.GetUnsafeReadOnlyPtrAndLength(childChunk.IndexInChunk, out var size);
659+
((int*) tempDataPtr)[0] = size;
660+
((int*) tempDataPtr)[1] = bufferBackupDataOffset;
661+
if (size > 0)
662+
UnsafeUtility.MemCpy(bufferBackupDataPtr + bufferBackupDataOffset, (byte*) bufferPtr, size * bufElemSize);
663+
bufferBackupDataOffset += size * bufElemSize;
664+
}
665+
else
666+
{
667+
//reset the entry to 0. Don't use memcpy in this case (is faster this way)
668+
((long*) tempDataPtr)[0] = 0;
668669
}
669670

670-
bufferBackupDataOffset = GhostComponentSerializer.SnapshotSizeAligned(bufferBackupDataOffset);
671+
tempDataPtr += compSize;
671672
}
672673

673-
dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity);
674+
bufferBackupDataOffset = GhostComponentSerializer.SnapshotSizeAligned(bufferBackupDataOffset);
674675
}
676+
677+
dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity);
675678
}
676679
}
677680
}

Runtime/Snapshot/GhostUpdateSystem.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,17 @@ bool RestorePredictionBackup(ArchetypeChunk chunk, int ent, in GhostCollectionPr
756756
var compSize = GhostComponentCollection[serializerIdx].ComponentType.IsBuffer
757757
? GhostSystemConstants.DynamicBufferComponentSnapshotSize
758758
: GhostComponentCollection[serializerIdx].ComponentSize;
759-
if (compSize == 0 || !chunk.Has(ref ghostChunkComponentTypesPtr[compIdx]))
759+
760+
//If the component does not have any ghost fields (so nothing to restore)
761+
//we don't need to restore the data and we don't need to advance the
762+
//data ptr either. No space has been reserved for this component in the backup buffer, see the
763+
//GhostPredictionHistorySystem)
764+
if (!GhostComponentCollection[serializerIdx].HasGhostFields)
765+
{
766+
continue;
767+
}
768+
769+
if (!chunk.Has(ref ghostChunkComponentTypesPtr[compIdx]))
760770
{
761771
dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity);
762772
continue;
@@ -837,7 +847,14 @@ bool RestorePredictionBackup(ArchetypeChunk chunk, int ent, in GhostCollectionPr
837847
enabledBitPtr = PredictionBackupState.GetNextEnabledBits(enabledBitPtr, chunk.Capacity);
838848
}
839849

840-
if (compSize == 0 || (GhostComponentCollection[serializerIdx].SendToOwner & requiredOwnerMask) == 0)
850+
//If the component does not have any ghost fields (so nothing to restore)
851+
//we don't need to restore the data and we don't need to advance the
852+
//data ptr either. No space has been reserved for this component in the backup buffer, see the
853+
//GhostPredictionHistorySystem)
854+
if(!GhostComponentCollection[serializerIdx].HasGhostFields)
855+
continue;
856+
857+
if ((GhostComponentCollection[serializerIdx].SendToOwner & requiredOwnerMask) == 0)
841858
{
842859
dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity);
843860
continue;

0 commit comments

Comments
 (0)