UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
FastArraySerializer.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3/*=============================================================================
4 NetworkSerialization.h:
5 Contains custom network serialization functionality.
6=============================================================================*/
7
8#pragma once
9
10#include "CoreMinimal.h"
11#include "Stats/Stats.h"
13#include "UObject/Class.h"
15#include "Misc/NetworkGuid.h"
16#include "UObject/CoreNet.h"
21#include "HAL/IConsoleManager.h"
22#include "Templates/EnableIf.h"
24
25#include "FastArraySerializer.generated.h"
26
27class Error;
30
31// Override this to define the compiled log level of LogNetFastTArray and reduce the amount of logs generated with each FastArray implementation
32#ifndef UE_FAST_ARRAY_COMPILE_LOG_LEVEL
33 #define UE_FAST_ARRAY_COMPILE_LOG_LEVEL All
34#endif
35
36// Runtime log level must be <= compiled log level
37#ifndef UE_FAST_ARRAY_RUNTIME_LOG_LEVEL
38 #define UE_FAST_ARRAY_RUNTIME_LOG_LEVEL Warning
39#endif
40
42
46
48
60#if 0
61
63USTRUCT()
65{
67
68 // Your data:
69 UPROPERTY()
71
72 UPROPERTY()
74
75
86
87 // Optional: debug string used with LogNetFastTArray logging
88 FString GetDebugString();
89
90};
91
93USTRUCT()
95{
97
98 UPROPERTY()
102 bool NetDeltaSerialize(FNetDeltaSerializeInfo & DeltaParms)
103 {
104 return FFastArraySerializer::FastArrayDeltaSerialize<FExampleItemEntry, FExampleArray>( Items, DeltaParms, *this );
105 }
106};
107
109template<>
110struct TStructOpsTypeTraits< FExampleArray > : public TStructOpsTypeTraitsBase2< FExampleArray >
111{
112 enum
113 {
115 };
116};
117
118#endif
119
230template <typename FastArrayType>
232{
233private:
234 struct CGetFastArrayItemTypeFuncable
235 {
236 template <typename InFastArrayType, typename...>
237 auto Requires(InFastArrayType* FastArray) -> decltype(FastArray->GetFastArrayItemTypePtr());
238 };
239
240 // Helper to always return a Type even if GetFastArrayItemTypePtr is not defined
241 static constexpr auto FastArrayTypePtr = []
242 {
244 {
245 return FastArrayType::GetFastArrayItemTypePtr();
246 }
247 else
248 {
249 return static_cast<int*>(nullptr);
250 }
251 }();
252
253public:
254 using FastArrayItemType = typename TRemovePointer<typename std::decay<decltype(FastArrayTypePtr)>::type>::Type;
255
260};
261
264{
265public:
266
268 : ArrayReplicationKey(INDEX_NONE)
269 {}
270
272 {
274 for (auto It = IDToCLMap.CreateIterator(); It; ++It)
275 {
276 auto Ptr = Other->IDToCLMap.Find(It.Key());
277 if (!Ptr || *Ptr != It.Value())
278 {
279 return false;
280 }
281 }
282 return true;
283 }
284
285 virtual void CountBytes(FArchive& Ar) const override
286 {
287 IDToCLMap.CountBytes(Ar);
288 }
289
292
294};
295
297USTRUCT()
299{
301
303 : ReplicationID(INDEX_NONE), ReplicationKey(INDEX_NONE), MostRecentArrayReplicationKey(INDEX_NONE)
304 {
305
306 }
307
309 : ReplicationID(INDEX_NONE), ReplicationKey(INDEX_NONE), MostRecentArrayReplicationKey(INDEX_NONE)
310 {
311
312 }
313
315 {
316 if (&In != this)
317 {
318 ReplicationID = INDEX_NONE;
319 ReplicationKey = INDEX_NONE;
320 MostRecentArrayReplicationKey = INDEX_NONE;
321 }
322 return *this;
323 }
324
325 UPROPERTY(NotReplicated)
326 int32 ReplicationID;
327
328 UPROPERTY(NotReplicated)
329 int32 ReplicationKey;
330
331 UPROPERTY(NotReplicated)
332 int32 MostRecentArrayReplicationKey;
333
341 inline void PreReplicatedRemove(const struct FFastArraySerializer& InArraySerializer) { }
357
364 inline FString GetDebugString() { return FString(TEXT("")); }
365};
366
382
393
394UENUM()
405
407USTRUCT()
409{
411
414
417
419 UPROPERTY(NotReplicated, Transient)
420 int32 IDCounter;
421
423 UPROPERTY(NotReplicated)
424 int32 ArrayReplicationKey;
425
428
430 TMap<int32, FGuidReferencesMap> GuidReferencesMap_StructDelta;
431
432#if WITH_PUSH_MODEL
433 // Object that is replicating this fast array
434 UObject* OwningObject;
435
436 // Property index of this array in the owning object's replication layout
437 int32 RepIndex;
438#endif // WITH_PUSH_MODEL
439
442 {
443 if (Item.ReplicationID == INDEX_NONE)
444 {
445 Item.ReplicationID = ++IDCounter;
446 if (IDCounter == INDEX_NONE)
447 {
448 IDCounter++;
449 }
450 }
451
452 Item.ReplicationKey++;
453 MarkArrayDirty();
454 }
455
458 {
459 ItemMap.Reset(); // This allows to clients to add predictive elements to arrays without affecting replication.
460 IncrementArrayReplicationKey();
461
462 // Invalidate the cached item counts so that they're recomputed during the next write
463 CachedNumItems = INDEX_NONE;
464 CachedNumItemsToConsiderForWriting = INDEX_NONE;
465 }
466
468 {
469 ArrayReplicationKey++;
470 if (ArrayReplicationKey == INDEX_NONE)
471 {
472 ArrayReplicationKey++;
473 }
474
475#if WITH_PUSH_MODEL
476 if (OwningObject != nullptr && RepIndex != INDEX_NONE)
477 {
478 MARK_PROPERTY_DIRTY_UNSAFE(OwningObject, RepIndex);
479 }
480#endif // WITH_PUSH_MODEL
481 }
482
493 template<typename Type, typename SerializerType>
494 static bool FastArrayDeltaSerialize(TArray<Type>& Items, FNetDeltaSerializeInfo& Parms, SerializerType& ArraySerializer);
495
502
509
516
522 {
523 // This is the size that the array had before the receive.
525 UE_DEPRECATED(5.4, "This is unsafe to use and will be removed.")
526 uint32 bHasMoreUnmappedReferences : 1U;
527 };
528
529 // Intentionally commented out, as we do not want to call this method unless it is defined by the derived type
530 // void PostReplicatedReceive(const FPostReplicatedReceiveParameters& Parameters)
531
532 // Concept used to detect if PostReplicatedReceive is defined or not
534 {
535 template <typename InFastArrayType, typename...>
536 auto Requires(InFastArrayType* FastArray, const FFastArraySerializer::FPostReplicatedReceiveParameters& Parameters) -> decltype(FastArray->PostReplicatedReceive(Parameters));
537 };
538
543 template<typename Type, typename SerializerType>
544 bool ShouldWriteFastArrayItem(const Type& Item, const bool bIsWritingOnClient) const
545 {
546 return !bIsWritingOnClient || Item.ReplicationID != INDEX_NONE;
547 }
548
549 void SetDeltaSerializationEnabled(const bool bEnabled)
550 {
552 {
553 if (bEnabled)
554 {
556 }
557 else
558 {
560 }
561 }
562 else
563 {
564 UE_LOG(LogNetFastTArray, Log, TEXT("FFastArraySerializer::SetDeltaSerializationEnabled - Called after array has been serialized. Ignoring"));
565 }
566 }
567
569 {
570 return DeltaFlags;
571 }
572
574 {
575 return MaxNumberOfAllowedChangesPerUpdate;
576 }
577
579 {
580 return MaxNumberOfAllowedDeletionsPerUpdate;
581 }
582
583#if WITH_PUSH_MODEL
584 void CachePushModelState(UObject* Object, const uint16 PropertyRepIndex)
585 {
586 if (OwningObject == nullptr && RepIndex == INDEX_NONE)
587 {
588 OwningObject = Object;
589 RepIndex = PropertyRepIndex;
590 }
591 }
592#endif // WITH_PUSH_MODEL
593
594private:
595
596 NETCORE_API static int32 MaxNumberOfAllowedChangesPerUpdate;
597 NETCORE_API static int32 MaxNumberOfAllowedDeletionsPerUpdate;
598 NETCORE_API static FAutoConsoleVariableRef CVarMaxNumberOfAllowedChangesPerUpdate;
599 NETCORE_API static FAutoConsoleVariableRef CVarMaxNumberOfAllowedDeletionsPerUpdate;
600
602 struct FFastArraySerializerHeader
603 {
605 int32 ArrayReplicationKey;
606
608 int32 BaseReplicationKey;
609
611 int32 NumChanged;
612
618 TArray<int32, TInlineAllocator<8>> DeletedIndices;
619 };
620
625 template<typename Type, typename SerializerType>
626 struct TFastArraySerializeHelper
627 {
630
632 TArray<Type>& Items;
633
635 SerializerType& ArraySerializer;
636
639
645 void ConditionalRebuildItemMap();
646
648 int32 CalcNumItemsForConsideration() const;
649
651 void ConditionalLogSerializerState(const TMap<int32, int32>* OldIDToKeyMap) const;
652
661 bool ConditionalCreateNewDeltaState(const TMap<int32, int32>& OldIDToKeyMap, const int32 BaseReplicationKey);
662
668 void BuildChangedAndDeletedBuffers(
673
677 void BuildChangedAndDeletedBuffersFromDefault(
680
682 void WriteDeltaHeader(FFastArraySerializerHeader& Header) const;
683
685 bool ReadDeltaHeader(FFastArraySerializerHeader& Header) const;
686
692 template<typename GuidMapType>
693 void PostReceiveCleanup(
694 FFastArraySerializerHeader& Header,
698
700 template<typename FastArrayType = SerializerType>
702 {
705 ArraySerializer.PostReplicatedReceive(PostReceivedParameters);
707 }
708
709 template<typename FastArrayType = SerializerType>
711
712 // Validate that deduced FastArrayItemType is valid and that it is the same as the specified one
713 static_assert(std::is_same_v<typename TFastArrayTypeHelper<SerializerType>::FastArrayItemType, Type>, "Auto deduced FastArrayItemType is invalid or differs from the specified type. Make sure that the FastArraySerializer has a single replicated array property.");
714 };
715
734 template<typename Type, typename SerializerType>
735 static bool FastArrayDeltaSerialize_DeltaSerializeStructs(TArray<Type>& Items, FNetDeltaSerializeInfo& Parms, SerializerType& ArraySerializer);
736
737private:
738
739 // Cached item counts, used for fast sanity checking when writing.
740 int32 CachedNumItems;
741 int32 CachedNumItemsToConsiderForWriting;
742
743 UPROPERTY(NotReplicated, Transient)
745};
746
747template<typename Type, typename SerializerType>
748void FFastArraySerializer::TFastArraySerializeHelper<Type, SerializerType>::ConditionalRebuildItemMap()
749{
750 if ((Parms.bUpdateUnmappedObjects || Parms.Writer == NULL) && ArraySerializer.ItemMap.Num() != Items.Num())
751 {
753 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize: Recreating Items map. Struct: %s, Items.Num: %d Map.Num: %d"), *Struct->GetOwnerStruct()->GetName(), Items.Num(), ArraySerializer.ItemMap.Num());
754 ArraySerializer.ItemMap.Empty();
755 for (int32 i = 0; i < Items.Num(); ++i)
756 {
757 if (Items[i].ReplicationID == INDEX_NONE)
758 {
759 if (Parms.Writer)
760 {
761 UE_LOG(LogNetFastTArray, Warning, TEXT("FastArrayDeltaSerialize: Item with uninitialized ReplicationID. Struct: %s, ItemIndex: %i"), *Struct->GetOwnerStruct()->GetName(), i);
762 }
763 else
764 {
765 // This is benign for clients, they may add things to their local array without assigning a ReplicationID
766 continue;
767 }
768 }
769
770 ArraySerializer.ItemMap.Add(Items[i].ReplicationID, i);
771 }
772 }
773}
774
775template<typename Type, typename SerializerType>
776int32 FFastArraySerializer::TFastArraySerializeHelper<Type, SerializerType>::CalcNumItemsForConsideration() const
777{
778 int32 Count = 0;
779
780 // Count the number of items in the current array that may be written. On clients, items that were predicted will be skipped.
781 for (const Type& Item : Items)
782 {
783 if (ArraySerializer.template ShouldWriteFastArrayItem<Type, SerializerType>(Item, Parms.bIsWritingOnClient))
784 {
785 Count++;
786 }
787 }
788
789 return Count;
790};
791
792template<typename Type, typename SerializerType>
793void FFastArraySerializer::TFastArraySerializeHelper<Type, SerializerType>::ConditionalLogSerializerState(const TMap<int32, int32>* OldIDToKeyMap) const
794{
795 // Log out entire state of current/base state
797 {
798 FString CurrentState = FString::Printf(TEXT("Current: %d "), ArraySerializer.ArrayReplicationKey);
799 for (const Type& Item : Items)
800 {
801 CurrentState += FString::Printf(TEXT("[%d/%d], "), Item.ReplicationID, Item.ReplicationKey);
802 }
803 UE_LOG(LogNetFastTArray, Log, TEXT("%s"), *CurrentState);
804
805
806 FString ClientStateStr = FString::Printf(TEXT("Client: %d "), Parms.OldState ? ((FNetFastTArrayBaseState*)Parms.OldState)->ArrayReplicationKey : 0);
807 if (OldIDToKeyMap)
808 {
810 {
811 ClientStateStr += FString::Printf(TEXT("[%d/%d], "), KeyValuePair.Key, KeyValuePair.Value);
812 }
813 }
815 }
816}
817
818template<typename Type, typename SerializerType>
819bool FFastArraySerializer::TFastArraySerializeHelper<Type, SerializerType>::ConditionalCreateNewDeltaState(const TMap<int32, int32>& OldIDToKeyMap, const int32 BaseReplicationKey)
820{
821 if (ArraySerializer.ArrayReplicationKey == BaseReplicationKey)
822 {
823 // If the keys didn't change, only update the item count caches if necessary.
824 if (ArraySerializer.CachedNumItems == INDEX_NONE ||
825 ArraySerializer.CachedNumItems != Items.Num() ||
826 ArraySerializer.CachedNumItemsToConsiderForWriting == INDEX_NONE)
827 {
828 ArraySerializer.CachedNumItems = Items.Num();
829 ArraySerializer.CachedNumItemsToConsiderForWriting = CalcNumItemsForConsideration();
830 }
831
832 if (UNLIKELY(OldIDToKeyMap.Num() != ArraySerializer.CachedNumItemsToConsiderForWriting))
833 {
834 UE_LOG(LogNetFastTArray, Warning, TEXT("OldMap size (%d) does not match item count (%d) for struct (%s), missing a MarkArrayDirty on element add/remove?"), OldIDToKeyMap.Num(), ArraySerializer.CachedNumItemsToConsiderForWriting, *Struct->GetOwnerStruct()->GetName());
835 }
836
837 if (Parms.OldState)
838 {
839 // Nothing changed and we had a valid old state, so just use/share the existing state. No need to create a new one.
840 *Parms.NewState = Parms.OldState->AsShared();
841 }
842 else
843 {
844 // Nothing changed but we don't have an existing state of our own yet so we need to make one here.
846 *Parms.NewState = MakeShareable(NewState);
847 NewState->ArrayReplicationKey = ArraySerializer.ArrayReplicationKey;
848 }
849
850 return false;
851 }
852
853 return true;
854}
855
856template<typename Type, typename SerializerType>
857void FFastArraySerializer::TFastArraySerializeHelper<Type, SerializerType>::BuildChangedAndDeletedBuffersFromDefault(
860{
861 const int32 NumConsideredItems = CalcNumItemsForConsideration();
862
863 // Verify assumptions, we never expect the default state to have replicated and thus IDCounter and ArrayReplicationKey should be at the defaults
864 check(Parms.bIsInitializingBaseFromDefault);
865
866 //-----------------------------------------------------------------
867 // When initializing from default we assume that all items are new
868 //-----------------------------------------------------------------
869 for (int32 i = 0; i < Items.Num(); ++i)
870 {
871 Type& Item = Items[i];
872
873 UE_LOG(LogNetFastTArray, Log, TEXT(" Array[%d] - ID %d. CL %d."), i, Item.ReplicationID, Item.ReplicationKey);
874 if (!ArraySerializer.template ShouldWriteFastArrayItem<Type, SerializerType>(Item, Parms.bIsWritingOnClient))
875 {
876 // On clients, this will skip items that were added predictively.
877 continue;
878 }
879
880 // When initializing from the CDO or archetype we do not allow the state to be modified as this will potentially lead to mismatch in ID assignment
881 if (Item.ReplicationID == INDEX_NONE)
882 {
883 UE_LOG(LogNetFastTArray, Log, TEXT(" FastArraySerializer::BuildChangedAndDeletedBuffersFromDefault Item with uninitialized ReplicationID detected in. Struct: %s, ItemIndex: %i"), *Parms.DebugName, i);
884 continue;
885 }
886
887 NewIDToKeyMap.Add(Item.ReplicationID, Item.ReplicationKey);
888
889 UE_LOG(LogNetFastTArray, Log, TEXT(" New! Element ID: %d. %s"), Item.ReplicationID, *Item.GetDebugString());
890 // New
891 ChangedElements.Add(FFastArraySerializer_FastArrayDeltaSerialize_FIdxIDPair(i, Item.ReplicationID));
892 }
893}
894
895template<typename Type, typename SerializerType>
896void FFastArraySerializer::TFastArraySerializeHelper<Type, SerializerType>::BuildChangedAndDeletedBuffers(
901{
902 ConditionalLogSerializerState(OldIDToKeyMap);
903
904 const int32 NumConsideredItems = CalcNumItemsForConsideration();
905
906 int32 DeleteCount = (OldIDToKeyMap ? OldIDToKeyMap->Num() : 0) - NumConsideredItems; // Note: this is incremented when we add new items below.
907 UE_LOG(LogNetFastTArray, Log, TEXT("NetSerializeItemDeltaFast: %s. DeleteCount: %d"), *Parms.DebugName, DeleteCount);
908
909 //--------------------------------------------
910 // Find out what is new or what has changed
911 //--------------------------------------------
912 for (int32 i = 0; i < Items.Num(); ++i)
913 {
914 Type& Item = Items[i];
915
916 UE_LOG(LogNetFastTArray, Log, TEXT(" Array[%d] - ID %d. CL %d."), i, Item.ReplicationID, Item.ReplicationKey);
917 if (!ArraySerializer.template ShouldWriteFastArrayItem<Type, SerializerType>(Item, Parms.bIsWritingOnClient))
918 {
919 // On clients, this will skip items that were added predictively.
920 continue;
921 }
922
923 // The item really should have a valid ReplicationID but in the case of loading from a save game, or initializing from a default state/archetype with existing data
924 // items may not have been marked dirty individually. Its ok to just assign them one here.
925 if (Item.ReplicationID == INDEX_NONE)
926 {
927 ArraySerializer.MarkItemDirty(Item);
928 }
929
930 NewIDToKeyMap.Add(Item.ReplicationID, Item.ReplicationKey);
931
932 const int32* OldValuePtr = OldIDToKeyMap ? OldIDToKeyMap->Find(Item.ReplicationID) : NULL;
933 if (OldValuePtr)
934 {
935 if (*OldValuePtr == Item.ReplicationKey)
936 {
937 UE_LOG(LogNetFastTArray, Log, TEXT(" Stayed The Same - Skipping"));
938
939 // Stayed the same, it might have moved but we dont care
940 continue;
941 }
942 else
943 {
944 UE_LOG(LogNetFastTArray, Log, TEXT(" Changed! Was: %d. Element ID: %d. %s"), *OldValuePtr, Item.ReplicationID, *Item.GetDebugString());
945
946 // Changed
947 ChangedElements.Add(FFastArraySerializer_FastArrayDeltaSerialize_FIdxIDPair(i, Item.ReplicationID));
948 }
949 }
950 else
951 {
952 UE_LOG(LogNetFastTArray, Log, TEXT(" New! Element ID: %d. %s"), Item.ReplicationID, *Item.GetDebugString());
953
954 // New
955 ChangedElements.Add(FFastArraySerializer_FastArrayDeltaSerialize_FIdxIDPair(i, Item.ReplicationID));
956 ++DeleteCount; // We added something new, so our initial DeleteCount value must be incremented.
957 }
958 }
959
960 // Find out what was deleted
961 if (DeleteCount > 0 && OldIDToKeyMap)
962 {
963 for (auto It = OldIDToKeyMap->CreateConstIterator(); It; ++It)
964 {
965 if (!NewIDToKeyMap.Contains(It.Key()))
966 {
967 UE_LOG(LogNetFastTArray, Log, TEXT(" Deleting ID: %d"), It.Key());
968
969 DeletedElements.Add(It.Key());
970 if (--DeleteCount <= 0)
971 {
972 break;
973 }
974 }
975 }
976 }
977}
978
979template<typename Type, typename SerializerType>
980void FFastArraySerializer::TFastArraySerializeHelper<Type, SerializerType>::WriteDeltaHeader(FFastArraySerializerHeader& Header) const
981{
982 FBitWriter& Writer = *Parms.Writer;
983
984 Writer << Header.ArrayReplicationKey;
985 Writer << Header.BaseReplicationKey;
986
987 int32 NumDeletes = Header.DeletedIndices.Num();
988 Writer << NumDeletes;
989
990 Writer << Header.NumChanged;
991
992 UE_LOG(LogNetFastTArray, Log, TEXT(" Writing Bunch. NumChange: %d. NumDel: %d [%d/%d]"),
993 Header.NumChanged, Header.DeletedIndices.Num(), Header.ArrayReplicationKey, Header.BaseReplicationKey);
994
997
998 // TODO: We should consider propagating this error in the same way we handle
999 // array overflows in RepLayout SendProperties / CompareProperties.
1000 UE_CLOG(NumDeletes > MaxNumDeleted, LogNetFastTArray, Warning, TEXT("NumDeletes > GetMaxNumberOfAllowedDeletionsPerUpdate: %d > %d. (Write)"), NumDeletes, MaxNumDeleted);
1001 UE_CLOG(Header.NumChanged > MaxNumChanged, LogNetFastTArray, Warning, TEXT("NumChanged > GetMaxNumberOfAllowedChangesPerUpdate: %d > %d. (Write)"), Header.NumChanged, MaxNumChanged);
1002
1003 // Serialize deleted items, just by their ID
1004 for (auto It = Header.DeletedIndices.CreateIterator(); It; ++It)
1005 {
1006 int32 ID = *It;
1007 Writer << ID;
1008 UE_LOG(LogNetFastTArray, Log, TEXT(" Deleted ElementID: %d"), ID);
1009 }
1010}
1011
1012template<typename Type, typename SerializerType>
1013bool FFastArraySerializer::TFastArraySerializeHelper<Type, SerializerType>::ReadDeltaHeader(FFastArraySerializerHeader& Header) const
1014{
1015 FBitReader& Reader = *Parms.Reader;
1016
1017 //---------------
1018 // Read header
1019 //---------------
1020
1021 Reader << Header.ArrayReplicationKey;
1022 Reader << Header.BaseReplicationKey;
1023
1024 int32 NumDeletes = 0;
1025 Reader << NumDeletes;
1026
1027 UE_LOG(LogNetFastTArray, Log, TEXT("Received [%d/%d]."), Header.ArrayReplicationKey, Header.BaseReplicationKey);
1028
1031 {
1032 UE_LOG(LogNetFastTArray, Warning, TEXT("NumDeletes > GetMaxNumberOfAllowedDeletionsPerUpdate: %d > %d. (Read)"), NumDeletes, MaxNumDeleted);
1033 Reader.SetError();
1034 return false;
1035 }
1036
1037 Reader << Header.NumChanged;
1038
1040 if (Header.NumChanged > MaxNumChanged)
1041 {
1042 UE_LOG(LogNetFastTArray, Warning, TEXT("NumChanged > GetMaxNumberOfAllowedChangesPerUpdate: %d > %d. (Read)"), Header.NumChanged, MaxNumChanged);
1043 Reader.SetError();
1044 return false;
1045 }
1046
1047 UE_LOG(LogNetFastTArray, Log, TEXT("Read NumChanged: %d NumDeletes: %d."), Header.NumChanged, NumDeletes);
1048
1049 //---------------
1050 // Read deleted elements
1051 //---------------
1052 if (NumDeletes > 0)
1053 {
1054 for (int32 i = 0; i < NumDeletes; ++i)
1055 {
1056 int32 ElementID = 0;
1057 Reader << ElementID;
1058
1059 int32* ElementIndexPtr = ArraySerializer.ItemMap.Find(ElementID);
1060 if (ElementIndexPtr)
1061 {
1063 Header.DeletedIndices.Add(DeleteIndex);
1064 UE_LOG(LogNetFastTArray, Log, TEXT(" Adding ElementID: %d for deletion"), ElementID);
1065 }
1066 else
1067 {
1068 UE_LOG(LogNetFastTArray, Log, TEXT(" Couldn't find ElementID: %d for deletion!"), ElementID);
1069 }
1070 }
1071 }
1072
1073 return true;
1074}
1075
1076template<typename Type, typename SerializerType>
1077template<typename GuidMapType>
1078void FFastArraySerializer::TFastArraySerializeHelper<Type, SerializerType>::PostReceiveCleanup(
1079 FFastArraySerializerHeader& Header,
1083{
1085
1086 // ---------------------------------------------------------
1087 // Look for implicit deletes that would happen due to Naks
1088 // ---------------------------------------------------------
1089
1090 // If we're sending data completely reliably, there's no need to do this.
1091 if (!Parms.bInternalAck)
1092 {
1093 for (int32 idx = 0; idx < Items.Num(); ++idx)
1094 {
1095 Type& Item = Items[idx];
1096 if (Item.MostRecentArrayReplicationKey < Header.ArrayReplicationKey && Item.MostRecentArrayReplicationKey > Header.BaseReplicationKey)
1097 {
1098 // Make sure this wasn't an explicit delete in this bunch (otherwise we end up deleting an extra element!)
1099 if (!Header.DeletedIndices.Contains(idx))
1100 {
1101 // This will happen in normal conditions in network replays.
1102 UE_LOG(LogNetFastTArray, Log, TEXT("Adding implicit delete for ElementID: %d. MostRecentArrayReplicationKey: %d. Current Payload: [%d/%d]"),
1103 Item.ReplicationID, Item.MostRecentArrayReplicationKey, Header.ArrayReplicationKey, Header.BaseReplicationKey);
1104
1105 Header.DeletedIndices.Add(idx);
1106 }
1107 }
1108 }
1109 }
1110
1111 // Increment keys so that a client can re-serialize the array if needed, such as for client replay recording.
1112 // Must check the size of DeleteIndices instead of NumDeletes to handle implicit deletes.
1113 if (Header.DeletedIndices.Num() > 0 || Header.NumChanged > 0)
1114 {
1115 ArraySerializer.IncrementArrayReplicationKey();
1116 }
1117
1118 // ---------------------------------------------------------
1119 // Invoke all callbacks: removed -> added -> changed
1120 // ---------------------------------------------------------
1121
1122 int32 PreRemoveSize = Items.Num();
1123 int32 FinalSize = PreRemoveSize - Header.DeletedIndices.Num();
1124 for (int32 idx : Header.DeletedIndices)
1125 {
1126 if (Items.IsValidIndex(idx))
1127 {
1128 // Remove the deleted element's tracked GUID references
1129 if (GuidMap.Remove(Items[idx].ReplicationID) > 0)
1130 {
1131 Parms.bGuidListsChanged = true;
1132 }
1133
1134 // Call the delete callbacks now, actually remove them at the end
1135#if UE_SUPPORT_FOR_ACTOR_TICK_DISABLE
1136 if (Parms.bCallFastArrayClientCallbacks)
1137#endif
1138 {
1139 Items[idx].PreReplicatedRemove(ArraySerializer);
1140 }
1141 }
1142 }
1143
1144#if UE_SUPPORT_FOR_ACTOR_TICK_DISABLE
1145 if (Parms.bCallFastArrayClientCallbacks)
1146#endif
1147 {
1148 ArraySerializer.PreReplicatedRemove(Header.DeletedIndices, FinalSize);
1149 }
1150
1151 if (PreRemoveSize != Items.Num())
1152 {
1153 UE_LOG(LogNetFastTArray, Error, TEXT("Item size changed after PreReplicatedRemove! PremoveSize: %d Item.Num: %d"),
1154 PreRemoveSize, Items.Num());
1155 }
1156
1157#if UE_SUPPORT_FOR_ACTOR_TICK_DISABLE
1158 if (Parms.bCallFastArrayClientCallbacks)
1159#endif
1160 {
1161 for (int32 idx : AddedIndices)
1162 {
1163 Items[idx].PostReplicatedAdd(ArraySerializer);
1164 }
1165 ArraySerializer.PostReplicatedAdd(AddedIndices, FinalSize);
1166 }
1167
1168#if UE_SUPPORT_FOR_ACTOR_TICK_DISABLE
1169 if (Parms.bCallFastArrayClientCallbacks)
1170#endif
1171 {
1172 for (int32 idx : ChangedIndices)
1173 {
1174 Items[idx].PostReplicatedChange(ArraySerializer);
1175 }
1176 ArraySerializer.PostReplicatedChange(ChangedIndices, FinalSize);
1177 }
1178
1179 if (PreRemoveSize != Items.Num())
1180 {
1181 UE_LOG(LogNetFastTArray, Error, TEXT("Item size changed after PostReplicatedAdd/PostReplicatedChange! PremoveSize: %d Item.Num: %d"),
1182 PreRemoveSize, Items.Num());
1183 }
1184
1185 if (Header.DeletedIndices.Num() > 0)
1186 {
1187 Header.DeletedIndices.Sort();
1188 for (int32 i = Header.DeletedIndices.Num() - 1; i >= 0; --i)
1189 {
1190 int32 DeleteIndex = Header.DeletedIndices[i];
1191 if (Items.IsValidIndex(DeleteIndex))
1192 {
1193 Items.RemoveAtSwap(DeleteIndex, EAllowShrinking::No);
1194
1195 UE_LOG(LogNetFastTArray, Log, TEXT(" Deleting: %d"), DeleteIndex);
1196 }
1197 }
1198
1199 // Clear the map now that the indices are all shifted around. This kind of sucks, we could use slightly better data structures here I think.
1200 // This will force the ItemMap to be rebuilt for the current Items array
1201 ArraySerializer.ItemMap.Empty();
1202 }
1203}
1204
1206template<typename Type, typename SerializerType >
1207bool FFastArraySerializer::FastArrayDeltaSerialize(TArray<Type> &Items, FNetDeltaSerializeInfo& Parms, SerializerType& ArraySerializer)
1208{
1209 // It's possible that we end up calling this method on clients before we've actually received
1210 // anything from the server (Net Conditions, Static Actors, etc.)
1211 // That should be fine though, because none of the GUID Tracking work should actually do anything
1212 // until after we've received.
1214 {
1215 return FastArrayDeltaSerialize_DeltaSerializeStructs(Items, Parms, ArraySerializer);
1216 }
1217
1219 class UScriptStruct* InnerStruct = Type::StaticStruct();
1220
1221 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize for %s. %s. %s"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName(), Parms.Reader ? TEXT("Reading") : TEXT("Writing"));
1222
1224 InnerStruct,
1225 Items,
1226 ArraySerializer,
1227 Parms
1228 };
1229
1230 //---------------
1231 // Build ItemMap if necessary. This maps ReplicationID to our local index into the Items array.
1232 //---------------
1233 Helper.ConditionalRebuildItemMap();
1234
1235 if ( Parms.GatherGuidReferences )
1236 {
1237 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize for %s. %s. GatherGuidReferences"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1238
1239 // Loop over all tracked guids, and return what we have
1240 for ( const auto& GuidReferencesPair : ArraySerializer.GuidReferencesMap )
1241 {
1242 const FFastArraySerializerGuidReferences& GuidReferences = GuidReferencesPair.Value;
1243
1244 Parms.GatherGuidReferences->Append( GuidReferences.UnmappedGUIDs );
1245 Parms.GatherGuidReferences->Append( GuidReferences.MappedDynamicGUIDs );
1246
1247 if ( Parms.TrackedGuidMemoryBytes )
1248 {
1249 *Parms.TrackedGuidMemoryBytes += GuidReferences.Buffer.Num();
1250 }
1251 }
1252
1253 return true;
1254 }
1255
1256 if ( Parms.MoveGuidToUnmapped )
1257 {
1258 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize for %s. %s. MovedGuidToUnmapped"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1259
1260 bool bFound = false;
1261
1262 const FNetworkGUID GUID = *Parms.MoveGuidToUnmapped;
1263
1264 // Try to find the guid in the list, and make sure it's on the unmapped lists now
1265 for ( auto& GuidReferencesPair : ArraySerializer.GuidReferencesMap )
1266 {
1268
1269 if ( GuidReferences.MappedDynamicGUIDs.Contains( GUID ) )
1270 {
1271 GuidReferences.MappedDynamicGUIDs.Remove( GUID );
1272 GuidReferences.UnmappedGUIDs.Add( GUID );
1273 bFound = true;
1274 }
1275 }
1276
1277 return bFound;
1278 }
1279
1280 if ( Parms.bUpdateUnmappedObjects )
1281 {
1282 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize for %s. %s. UpdateUnmappedObjects"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1283
1285
1286 // Loop over each item that has unmapped objects
1287 for ( auto It = ArraySerializer.GuidReferencesMap.CreateIterator(); It; ++It )
1288 {
1289 // Get the element id
1290 const int32 ElementID = It.Key();
1291
1292 // Get a reference to the unmapped item itself
1293 FFastArraySerializerGuidReferences& GuidReferences = It.Value();
1294
1295 if ( ( GuidReferences.UnmappedGUIDs.Num() == 0 && GuidReferences.MappedDynamicGUIDs.Num() == 0 ) || ArraySerializer.ItemMap.Find( ElementID ) == NULL )
1296 {
1297 // If for some reason the item is gone (or all guids were removed), we don't need to track guids for this item anymore
1298 It.RemoveCurrent();
1299 continue; // We're done with this unmapped item
1300 }
1301
1302 // Loop over all the guids, and check to see if any of them are loaded yet
1303 bool bMappedSomeGUIDs = false;
1304
1305 for ( auto UnmappedIt = GuidReferences.UnmappedGUIDs.CreateIterator(); UnmappedIt; ++UnmappedIt )
1306 {
1307 const FNetworkGUID& GUID = *UnmappedIt;
1308
1309 if ( Parms.Map->IsGUIDBroken( GUID, false ) )
1310 {
1311 // Stop trying to load broken guids
1312 UE_LOG( LogNetFastTArray, Warning, TEXT( "FastArrayDeltaSerialize: Broken GUID. NetGuid: %s" ), *GUID.ToString() );
1313 UnmappedIt.RemoveCurrent();
1314 continue;
1315 }
1316
1317 UObject* Object = Parms.Map->GetObjectFromNetGUID( GUID, false );
1318
1319 if ( Object != NULL )
1320 {
1321 // This guid loaded!
1322 if ( GUID.IsDynamic() )
1323 {
1324 GuidReferences.MappedDynamicGUIDs.Add( GUID ); // Move back to mapped list
1325 }
1326 UnmappedIt.RemoveCurrent();
1327 bMappedSomeGUIDs = true;
1328 }
1329 }
1330
1331 // Check to see if we loaded any guids. If we did, we can serialize the element again which will load it this time
1332 if ( bMappedSomeGUIDs )
1333 {
1334 Parms.bOutSomeObjectsWereMapped = true;
1335
1336 if ( !Parms.bCalledPreNetReceive )
1337 {
1338 // Call PreNetReceive if we are going to change a value (some game code will need to think this is an actual replicated value)
1339 Parms.Object->PreNetReceive();
1340 Parms.bCalledPreNetReceive = true;
1341 }
1342
1343 const int32 ElementIndex = ArraySerializer.ItemMap.FindChecked(ElementID);
1344 Type* ThisElement = &Items[ElementIndex];
1345
1346 ChangedIndices.Add(ElementIndex);
1347
1348 // Initialize the reader with the stored buffer that we need to read from
1349 FNetBitReader Reader( Parms.Map, GuidReferences.Buffer.GetData(), GuidReferences.NumBufferBits );
1350
1351 // Read the property (which should serialize any newly mapped objects as well)
1352 Parms.Struct = InnerStruct;
1353 Parms.Data = ThisElement;
1354 Parms.Reader = &Reader;
1355 Parms.NetSerializeCB->NetSerializeStruct(Parms);
1356
1357#if UE_SUPPORT_FOR_ACTOR_TICK_DISABLE
1358 if (Parms.bCallFastArrayClientCallbacks)
1359#endif
1360 {
1361 // Let the element know it changed
1362 ThisElement->PostReplicatedChange(ArraySerializer);
1363 }
1364 }
1365
1366 // If we have no more guids, we can remove this item for good
1367 if ( GuidReferences.UnmappedGUIDs.Num() == 0 && GuidReferences.MappedDynamicGUIDs.Num() == 0 )
1368 {
1369 It.RemoveCurrent();
1370 }
1371 }
1372
1373 // If we still have unmapped items, then communicate this to the outside
1374 if ( ArraySerializer.GuidReferencesMap.Num() > 0 )
1375 {
1376 Parms.bOutHasMoreUnmapped = true;
1377 }
1378
1379 if (Parms.bOutSomeObjectsWereMapped)
1380 {
1381#if UE_SUPPORT_FOR_ACTOR_TICK_DISABLE
1382 if (Parms.bCallFastArrayClientCallbacks)
1383#endif
1384 {
1385 ArraySerializer.PostReplicatedChange(ChangedIndices, Items.Num());
1386 Helper.CallPostReplicatedReceiveOrNot(Items.Num());
1387 }
1388 }
1389 return true;
1390 }
1391
1392 // If we've made it this far, it means that we're going to be serializing something.
1393 // So, it should be safe for us to update our cached state.
1394 // Also, make sure that we hit the right path if we need to.
1396 {
1397 ArraySerializer.DeltaFlags |= EFastArraySerializerDeltaFlags::HasBeenSerialized;
1399 {
1401 return FastArrayDeltaSerialize_DeltaSerializeStructs(Items, Parms, ArraySerializer);
1402 }
1403 }
1404
1405 if (Parms.Writer)
1406 {
1407 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize for %s. %s. Writing"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1408
1409 //-----------------------------
1410 // Saving
1411 //-----------------------------
1412 check(Parms.Struct);
1413 FBitWriter& Writer = *Parms.Writer;
1414
1415 // Get the old map if its there
1416 TMap<int32, int32> * OldMap = nullptr;
1417 int32 BaseReplicationKey = INDEX_NONE;
1418
1419 // See if the array changed at all. If the ArrayReplicationKey matches we can skip checking individual items
1420 if (Parms.OldState)
1421 {
1422 OldMap = &((FNetFastTArrayBaseState*)Parms.OldState)->IDToCLMap;
1423 BaseReplicationKey = ((FNetFastTArrayBaseState*)Parms.OldState)->ArrayReplicationKey;
1424
1425 // If we didn't create a new delta state, that implies nothing changed,
1426 // so we're done.
1427 if (!Helper.ConditionalCreateNewDeltaState(*OldMap, BaseReplicationKey))
1428 {
1429 return false;
1430 }
1431 }
1432
1433 // Create a new map from the current state of the array
1435
1436 check(Parms.NewState);
1437 *Parms.NewState = MakeShareable( NewState );
1438 TMap<int32, int32>& NewMap = NewState->IDToCLMap;
1439 NewState->ArrayReplicationKey = ArraySerializer.ArrayReplicationKey;
1440
1441 FFastArraySerializerHeader Header{
1442 ArraySerializer.ArrayReplicationKey,
1443 BaseReplicationKey,
1444 0
1445 };
1446
1448
1449 // When we are initializing from default we use a simplified variant of BuildChangedAndDeletedBuffers that does not modify the source state
1451 {
1452 Helper.BuildChangedAndDeletedBuffersFromDefault(NewMap, ChangedElements);
1453 }
1454 else
1455 {
1456 Helper.BuildChangedAndDeletedBuffers(NewMap, OldMap, ChangedElements, Header.DeletedIndices);
1457
1458 // The array replication key may have changed while adding new elements (in the call to BuildChangedAndDeletedBuffers above)
1459 NewState->ArrayReplicationKey = ArraySerializer.ArrayReplicationKey;
1460 }
1461
1462 // Note: we used to early return false here if nothing had changed, but we still need to send
1463 // a bunch with the array key / base key, so that clients can look for implicit deletes.
1464
1465 //----------------------
1466 // Write it out.
1467 //----------------------
1468
1469 Header.NumChanged = ChangedElements.Num();
1470 Helper.WriteDeltaHeader(Header);
1471
1472 // Serialized new elements with their payload
1473 for (auto It = ChangedElements.CreateIterator(); It; ++It)
1474 {
1475 void* ThisElement = &Items[It->Idx];
1476
1477 // Dont pack this, want property to be byte aligned
1478 uint32 ID = It->ID;
1479 Writer << ID;
1480
1481 UE_LOG(LogNetFastTArray, Log, TEXT(" Changed ElementID: %d"), ID);
1482
1483 Parms.Struct = InnerStruct;
1484 Parms.Data = ThisElement;
1485 Parms.NetSerializeCB->NetSerializeStruct(Parms);
1486 }
1487 }
1488 else
1489 {
1490 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize for %s. %s. Reading"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1491
1492 //-----------------------------
1493 // Loading
1494 //-----------------------------
1495 check(Parms.Reader);
1496 FBitReader& Reader = *Parms.Reader;
1497
1498 FFastArraySerializerHeader Header;
1499 if (!Helper.ReadDeltaHeader(Header))
1500 {
1501 return false;
1502 }
1503
1504 const int32 OldNumItems = Items.Num();
1507
1508 //---------------
1509 // Read Changed/New elements
1510 //---------------
1511 for(int32 i = 0; i < Header.NumChanged; ++i)
1512 {
1513 int32 ElementID = 0;
1514 Reader << ElementID;
1515
1516 int32* ElementIndexPtr = ArraySerializer.ItemMap.Find(ElementID);
1517 int32 ElementIndex = 0;
1518 Type* ThisElement = nullptr;
1519
1520 if (!ElementIndexPtr)
1521 {
1522 UE_LOG(LogNetFastTArray, Log, TEXT(" New. ID: %d. New Element!"), ElementID);
1523
1525 ThisElement->ReplicationID = ElementID;
1526
1527 ElementIndex = Items.Num()-1;
1528 ArraySerializer.ItemMap.Add(ElementID, ElementIndex);
1529
1530 AddedIndices.Add(ElementIndex);
1531 }
1532 else
1533 {
1534 UE_LOG(LogNetFastTArray, Log, TEXT(" Changed. ID: %d -> Idx: %d"), ElementID, *ElementIndexPtr);
1535 ElementIndex = *ElementIndexPtr;
1536 ThisElement = &Items[ElementIndex];
1537 ChangedIndices.Add(ElementIndex);
1538 }
1539
1540 // Update this element's most recent array replication key
1541 ThisElement->MostRecentArrayReplicationKey = Header.ArrayReplicationKey;
1542
1543 // Update this element's replication key so that a client can re-serialize the array for client replay recording
1544 ThisElement->ReplicationKey++;
1545
1546 // Let package map know we want to track and know about any guids that are unmapped during the serialize call
1547 Parms.Map->ResetTrackedGuids( true );
1548
1549 // Remember where we started reading from, so that if we have unmapped properties, we can re-deserialize from this data later
1550 FBitReaderMark Mark( Reader );
1551
1552 Parms.Struct = InnerStruct;
1553 Parms.Data = ThisElement;
1554 Parms.NetSerializeCB->NetSerializeStruct(Parms);
1555
1556 if ( !Reader.IsError() )
1557 {
1558 // Track unmapped guids
1561
1563 {
1564 FFastArraySerializerGuidReferences& GuidReferences = ArraySerializer.GuidReferencesMap.FindOrAdd( ElementID );
1565
1566 // If guid lists are different, make note of that, and copy respective list
1568 {
1569 // Copy the unmapped guid list to this unmapped item
1570 GuidReferences.UnmappedGUIDs = TrackedUnmappedGuids;
1571 Parms.bGuidListsChanged = true;
1572 }
1573
1575 {
1576 // Copy the mapped guid list
1578 Parms.bGuidListsChanged = true;
1579 }
1580
1581 GuidReferences.Buffer.Empty();
1582
1583 // Remember the number of bits in the buffer
1584 check(Reader.GetPosBits() - Mark.GetPos() <= TNumericLimits<int32>::Max());
1585 GuidReferences.NumBufferBits = int32(Reader.GetPosBits() - Mark.GetPos());
1586
1587 // Copy the buffer itself
1588 Mark.Copy( Reader, GuidReferences.Buffer );
1589
1590 // Hijack this property to communicate that we need to be tracked since we have some unmapped guids
1591 if ( TrackedUnmappedGuids.Num() )
1592 {
1593 Parms.bOutHasMoreUnmapped = true;
1594 }
1595 }
1596 else
1597 {
1598 // If we don't have any unmapped objects, make sure we're no longer tracking this item in the unmapped lists
1599 ArraySerializer.GuidReferencesMap.Remove( ElementID );
1600 }
1601 }
1602
1603 // Stop tracking unmapped objects
1604 Parms.Map->ResetTrackedGuids( false );
1605
1606 if ( Reader.IsError() )
1607 {
1608 UE_LOG( LogNetFastTArray, Warning, TEXT( "Parms.NetSerializeCB->NetSerializeStruct: Reader.IsError() == true" ) );
1609 return false;
1610 }
1611 }
1612
1613 Helper.template PostReceiveCleanup<>(Header, ChangedIndices, AddedIndices, ArraySerializer.GuidReferencesMap);
1614
1615#if UE_SUPPORT_FOR_ACTOR_TICK_DISABLE
1616 if (Parms.bCallFastArrayClientCallbacks)
1617#endif
1618 {
1619 Helper.CallPostReplicatedReceiveOrNot(OldNumItems);
1620 }
1621 }
1622
1623 return true;
1624}
1625
1643
1644template<typename Type, typename SerializerType>
1645bool FFastArraySerializer::FastArrayDeltaSerialize_DeltaSerializeStructs(TArray<Type>& Items, FNetDeltaSerializeInfo& Parms, SerializerType& ArraySerializer)
1646{
1658 {
1659 static void PreReplicatedRemove(void* FastArrayItem, const struct FFastArrayDeltaSerializeParams& Params)
1660 {
1661 reinterpret_cast<Type*>(FastArrayItem)->PreReplicatedRemove(static_cast<SerializerType&>(Params.ArraySerializer));
1662 }
1663
1664 static void PostReplicatedAdd(void* FastArrayItem, const struct FFastArrayDeltaSerializeParams& Params)
1665 {
1666 reinterpret_cast<Type*>(FastArrayItem)->PostReplicatedAdd(static_cast<SerializerType&>(Params.ArraySerializer));
1667 }
1668
1669 static void PostReplicatedChange(void* FastArrayItem, const struct FFastArrayDeltaSerializeParams& Params)
1670 {
1671 reinterpret_cast<Type*>(FastArrayItem)->PostReplicatedChange(static_cast<SerializerType&>(Params.ArraySerializer));
1672 }
1673
1674 static void ReceivedItem(void* FastArrayItem, const FFastArrayDeltaSerializeParams& Params, const uint32 ReplicationID)
1675 {
1676 Type* Item = reinterpret_cast<Type*>(FastArrayItem);
1677 Item->ReplicationID = ReplicationID;
1678 Item->MostRecentArrayReplicationKey = Params.ReadArrayReplicationKey;
1679 Item->ReplicationKey++;
1680 }
1681 };
1682
1684
1685 class UScriptStruct* InnerStruct = Type::StaticStruct();
1686
1688 InnerStruct,
1689 Items,
1690 ArraySerializer,
1691 Parms
1692 };
1693
1695 Parms,
1696 ArraySerializer,
1697 FFastArrayItemCallbackHelper::PreReplicatedRemove,
1698 FFastArrayItemCallbackHelper::PostReplicatedAdd,
1699 FFastArrayItemCallbackHelper::PostReplicatedChange,
1700 FFastArrayItemCallbackHelper::ReceivedItem,
1701 };
1702
1703 //---------------
1704 // Build ItemMap if necessary. This maps ReplicationID to our local index into the Items array.
1705 //---------------
1706 Helper.ConditionalRebuildItemMap();
1707
1708 if (Parms.GatherGuidReferences)
1709 {
1710 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize_DeltaSerializeStruct for %s. %s. GatherGuidReferences"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1712 return true;
1713 }
1714 else if (Parms.MoveGuidToUnmapped)
1715 {
1716 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize_DeltaSerializeStruct for %s. %s. MoveGuidToUnmapped"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1718 }
1719 else if (Parms.bUpdateUnmappedObjects)
1720 {
1721 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize_DeltaSerializeStruct for %s. %s. UpdateUnmappedObjects"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1722
1724
1725 DeltaSerializeParams.ReadChangedElements = &ChangedIndices;
1726
1728
1729#if UE_SUPPORT_FOR_ACTOR_TICK_DISABLE
1730 if (Parms.bCallFastArrayClientCallbacks)
1731#endif
1732 {
1733 if (Parms.bOutSomeObjectsWereMapped)
1734 {
1735 ArraySerializer.PostReplicatedChange(ChangedIndices, Items.Num());
1736 Helper.CallPostReplicatedReceiveOrNot(Items.Num());
1737 }
1738 }
1739
1740 return true;
1741 }
1742 else if (Parms.Writer)
1743 {
1744 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize_DeltaSerializeStruct for %s. %s. Writing"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1745
1746 //-----------------------------
1747 // Saving
1748 //-----------------------------
1749 check(Parms.Struct);
1750 FBitWriter& Writer = *Parms.Writer;
1751
1752 // Get the old map if its there
1753 TMap<int32, int32>* OldItemMap = nullptr;
1754 int32 BaseReplicationKey = INDEX_NONE;
1755
1756 // We set ChangelistHistory and LastAckedHistory to 0, as that's the default RepLayout expects.
1757 // Setting them to anything else may cause issues with initial sends.
1760
1761 // See if the array changed at all. If the ArrayReplicationKey matches we can skip checking individual items
1762 if (Parms.OldState)
1763 {
1764 OldItemMap = &((FNetFastTArrayBaseState*)Parms.OldState)->IDToCLMap;
1765 BaseReplicationKey = ((FNetFastTArrayBaseState*)Parms.OldState)->ArrayReplicationKey;
1768
1769 if (!Helper.ConditionalCreateNewDeltaState(*OldItemMap, BaseReplicationKey))
1770 {
1771 return false;
1772 }
1773 }
1774
1775 // Create a new map from the current state of the array
1777
1778 check(Parms.NewState);
1779
1780 *Parms.NewState = MakeShareable(NewState);
1781 NewState->ArrayReplicationKey = ArraySerializer.ArrayReplicationKey;
1782
1784
1785 FFastArraySerializerHeader Header{
1786 ArraySerializer.ArrayReplicationKey,
1787 BaseReplicationKey,
1788 0
1789 };
1790
1792
1793 // When we are initializing from default we use a simplified variant of BuildChangedAndDeletedBuffers that does not modify the source state
1795 {
1796 Helper.BuildChangedAndDeletedBuffersFromDefault(NewItemMap, ChangedElements);
1797 }
1798 else
1799 {
1800 Helper.BuildChangedAndDeletedBuffers(NewItemMap, OldItemMap, ChangedElements, Header.DeletedIndices);
1801
1802 // The array replication key may have changed while adding new elemnts (in the call to MarkItemDirty above)
1803 NewState->ArrayReplicationKey = ArraySerializer.ArrayReplicationKey;
1804 }
1805
1806 // Note: we used to early return false here if nothing had changed, but we still need to send
1807 // a bunch with the array key / base key, so that clients can look for implicit deletes.
1808
1809 //----------------------
1810 // Write it out.
1811 //----------------------
1812
1813 Header.NumChanged = ChangedElements.Num();
1814 Helper.WriteDeltaHeader(Header);
1815
1818
1819 DeltaSerializeParams.WriteChangedElements = &ChangedElements;
1820 DeltaSerializeParams.WriteBaseState = NewState;
1821
1823 }
1824 else
1825 {
1826 UE_LOG(LogNetFastTArray, Log, TEXT("FastArrayDeltaSerialize_DeltaSerializeStruct for %s. %s. Reading"), *InnerStruct->GetName(), *InnerStruct->GetOwnerStruct()->GetName());
1827
1828 //-----------------------------
1829 // Loading
1830 //-----------------------------
1831 check(Parms.Reader);
1832 FBitReader& Reader = *Parms.Reader;
1833
1834 FFastArraySerializerHeader Header;
1835 if (!Helper.ReadDeltaHeader(Header))
1836 {
1837 return false;
1838 }
1839
1840 const int32 OldNumItems = Items.Num();
1843
1844 DeltaSerializeParams.ReadAddedElements = &AddedIndices;
1845 DeltaSerializeParams.ReadChangedElements = &ChangedIndices;
1846 DeltaSerializeParams.ReadNumChanged = Header.NumChanged;
1847 DeltaSerializeParams.ReadArrayReplicationKey = Header.ArrayReplicationKey;
1848
1850 {
1851 return false;
1852 }
1853
1854 //---------------
1855 // Read Changed/New elements
1856 //---------------
1857
1858 Helper.template PostReceiveCleanup<>(Header, ChangedIndices, AddedIndices, ArraySerializer.GuidReferencesMap_StructDelta);
1859
1860#if UE_SUPPORT_FOR_ACTOR_TICK_DISABLE
1861 if (Parms.bCallFastArrayClientCallbacks)
1862#endif
1863 {
1864 Helper.CallPostReplicatedReceiveOrNot(OldNumItems);
1865 }
1866 }
1867
1868 return true;
1869}
1870
1871
1875#if UE_WITH_IRIS
1876#define UE_NET_DECLARE_FASTARRAY(FastArrayType, FastArrayItemArrayMemberName, Api) \
1877 static constexpr auto GetFastArrayItemTypePtr() { return static_cast<decltype(FastArrayType::FastArrayItemArrayMemberName)::ElementType*>(nullptr); }; \
1878 Api static UE::Net::CreateAndRegisterReplicationFragmentFunc GetFastArrayCreateReplicationFragmentFunction();
1879#else
1880#define UE_NET_DECLARE_FASTARRAY(FastArrayType, FastArrayItemArrayMemberName, Api) static constexpr auto GetFastArrayItemTypePtr() { return static_cast<decltype(FastArrayType::FastArrayItemArrayMemberName)::ElementType*>(nullptr); };;
1881#endif
1882
OODEFFUNC typedef void(OODLE_CALLBACK t_fp_OodleCore_Plugin_Free)(void *ptr)
#define NULL
Definition oodle2base.h:134
#define check(expr)
Definition AssertionMacros.h:314
@ INDEX_NONE
Definition CoreMiscDefines.h:150
#define UE_DEPRECATED(Version, Message)
Definition CoreMiscDefines.h:302
bool NetworkGuidSetsAreSame(const TSet< FNetworkGUID > &A, const TSet< FNetworkGUID > &B)
Definition CoreNet.h:453
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
#define UNLIKELY(x)
Definition Platform.h:857
#define DECLARE_CYCLE_STAT_EXTERN(CounterName, StatId, GroupId, API)
Definition Stats.h:679
#define SCOPE_CYCLE_COUNTER(Stat)
Definition Stats.h:650
#define CONDITIONAL_SCOPE_CYCLE_COUNTER(Stat, bCondition)
Definition Stats.h:654
SharedPointerInternals::TRawPtrProxy< ObjectType > MakeShareable(ObjectType *InObject)
Definition SharedPointer.h:1947
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define CSV_SCOPED_TIMING_STAT(Category, StatName)
Definition CsvProfiler.h:150
#define CSV_DECLARE_CATEGORY_MODULE_EXTERN(Module_API, CategoryName)
Definition CsvProfiler.h:80
constexpr bool EnumHasAnyFlags(Enum Flags, Enum Contains)
Definition EnumClassFlags.h:35
constexpr bool EnumHasAllFlags(Enum Flags, Enum Contains)
Definition EnumClassFlags.h:28
#define ENUM_CLASS_FLAGS(Enum)
Definition EnumClassFlags.h:6
EFastArraySerializerDeltaFlags
Definition FastArraySerializer.h:396
@ IsUsingDeltaSerialization
Set if users requested Delta Serialization for this struct.
@ HasDeltaBeenRequested
Set when serialization at least once (i.e., this struct has been written or read).
#define UE_FAST_ARRAY_RUNTIME_LOG_LEVEL
Definition FastArraySerializer.h:38
#define UE_FAST_ARRAY_COMPILE_LOG_LEVEL
Definition FastArraySerializer.h:33
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS
Definition GenericPlatformCompilerPreSetup.h:12
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS
Definition GenericPlatformCompilerPreSetup.h:8
#define DECLARE_LOG_CATEGORY_EXTERN(CategoryName, DefaultVerbosity, CompileTimeVerbosity)
Definition LogMacros.h:361
#define UE_CLOG(Condition, CategoryName, Verbosity, Format,...)
Definition LogMacros.h:298
#define UE_LOG_ACTIVE(CategoryName, Verbosity)
Definition LogMacros.h:255
#define UE_LOG(CategoryName, Verbosity, Format,...)
Definition LogMacros.h:270
bool GUseDetailedScopeCounters
Definition NetCoreModule.cpp:14
#define UPROPERTY(...)
UObject definition macros.
Definition ObjectMacros.h:744
#define UENUM(...)
Definition ObjectMacros.h:749
#define USTRUCT(...)
Definition ObjectMacros.h:746
#define GENERATED_USTRUCT_BODY(...)
Definition ObjectMacros.h:767
#define MARK_PROPERTY_DIRTY_UNSAFE(Object, RepIndex)
Definition PushModel.h:474
uint8_t uint8
Definition binka_ue_file_header.h:8
uint16_t uint16
Definition binka_ue_file_header.h:7
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition Archive.h:1208
CORE_API void SetError()
Definition Archive.cpp:314
UE_FORCEINLINE_HINT bool IsError() const
Definition Archive.h:362
Definition IConsoleManager.h:1580
Definition CoreNet.h:415
Definition FastArraySerializer.h:264
FNetFastTArrayBaseState()
Definition FastArraySerializer.h:267
virtual bool IsStateEqual(INetDeltaBaseState *OtherState)
Definition FastArraySerializer.h:271
TMap< int32, int32 > IDToCLMap
Definition FastArraySerializer.h:291
virtual void CountBytes(FArchive &Ar) const override
Definition FastArraySerializer.h:285
int32 ArrayReplicationKey
Definition FastArraySerializer.h:293
Definition NetworkGuid.h:12
Definition CoreNet.h:476
uint32 GetChangelistHistory() const
Definition CoreNet.h:498
void SetChangelistHistory(uint32 InChangelistHistory)
Definition CoreNet.h:499
void SetLastAckedHistory(uint32 InAckedHistory)
Definition CoreNet.h:496
uint32 GetLastAckedHistory() const
Definition CoreNet.h:495
virtual void GatherGuidReferencesForFastArray(struct FFastArrayDeltaSerializeParams &Params)=0
virtual void NetSerializeStruct(FNetDeltaSerializeInfo &Params)=0
virtual bool NetDeltaSerializeForFastArray(struct FFastArrayDeltaSerializeParams &Params)=0
virtual void UpdateUnmappedGuidsForFastArray(struct FFastArrayDeltaSerializeParams &Params)=0
virtual bool MoveGuidToUnmappedForFastArray(struct FFastArrayDeltaSerializeParams &Params)=0
Definition ArrayView.h:139
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
UE_NODEBUG TIterator CreateIterator()
Definition Array.h:3355
UE_NODEBUG UE_FORCEINLINE_HINT ElementType * GetData() UE_LIFETIMEBOUND
Definition Array.h:1027
UE_NODEBUG UE_FORCEINLINE_HINT SizeType Add(ElementType &&Item)
Definition Array.h:2696
ElementType & AddDefaulted_GetRef() UE_LIFETIMEBOUND
Definition Array.h:2815
void Empty(SizeType Slack=0)
Definition Array.h:2273
Definition EnableIf.h:20
Definition FastArraySerializer.h:232
typename TRemovePointer< typename std::decay< decltype(FastArrayTypePtr)>::type >::Type FastArrayItemType
Definition FastArraySerializer.h:254
static constexpr bool HasValidFastArrayItemType()
Definition FastArraySerializer.h:259
Definition AndroidPlatformMisc.h:14
Definition UnrealString.h.inl:34
Definition ContainerAllocationPolicies.h:894
COREUOBJECT_API UStruct * GetOwnerStruct() const
Definition Class.cpp:226
UE_FORCEINLINE_HINT FString GetName() const
Definition UObjectBaseUtility.h:439
Definition Object.h:95
virtual COREUOBJECT_API void PreNetReceive()
Definition Obj.cpp:5890
const TSet< FNetworkGUID > & GetTrackedDynamicMappedGuids() const
Definition CoreNet.h:224
virtual bool IsGUIDBroken(const FNetworkGUID &NetGUID, const bool bMustBeRegistered) const
Definition CoreNet.h:236
const TSet< FNetworkGUID > & GetTrackedUnmappedGuids() const
Definition CoreNet.h:223
virtual UObject * GetObjectFromNetGUID(const FNetworkGUID &NetGUID, const bool bIgnoreMustBeMapped)
Definition CoreNet.h:234
void ResetTrackedGuids(bool bShouldTrack)
Definition CoreNet.h:222
Definition Class.h:1720
Type
Definition PawnAction_Move.h:11
Definition BitReader.h:258
CORE_API void Copy(FBitReader &Reader, TArray< uint8 > &Buffer)
Definition BitReader.cpp:367
UE_FORCEINLINE_HINT int64 GetPos() const
Definition BitReader.h:269
Definition BitReader.h:25
UE_FORCEINLINE_HINT int64 GetPosBits() const
Definition BitReader.h:189
Definition BitWriter.h:22
Definition FastArraySerializer.h:1627
TArray< int32, TInlineAllocator< 8 > > * ReadChangedElements
Definition FastArraySerializer.h:1638
const TFunction< void(void *, const FFastArrayDeltaSerializeParams &)> PreReplicatedRemove
Definition FastArraySerializer.h:1631
const TFunction< void(void *, const FFastArrayDeltaSerializeParams &)> PostReplicatedChange
Definition FastArraySerializer.h:1633
FNetFastTArrayBaseState * WriteBaseState
Definition FastArraySerializer.h:1637
FNetDeltaSerializeInfo & DeltaSerializeInfo
Definition FastArraySerializer.h:1628
TArray< int32, TInlineAllocator< 8 > > * ReadAddedElements
Definition FastArraySerializer.h:1639
int32 ReadArrayReplicationKey
Definition FastArraySerializer.h:1641
const TFunction< void(void *, const FFastArrayDeltaSerializeParams &)> PostReplicatedAdd
Definition FastArraySerializer.h:1632
TArray< FFastArraySerializer_FastArrayDeltaSerialize_FIdxIDPair, TInlineAllocator< 8 > > * WriteChangedElements
Definition FastArraySerializer.h:1636
FFastArraySerializer & ArraySerializer
Definition FastArraySerializer.h:1629
const TFunction< void(void *, const FFastArrayDeltaSerializeParams &, const uint32)> ReceivedItem
Definition FastArraySerializer.h:1634
int32 ReadNumChanged
Definition FastArraySerializer.h:1640
Definition FastArraySerializer.h:369
TArray< uint8 > Buffer
Definition FastArraySerializer.h:377
TSet< FNetworkGUID > MappedDynamicGUIDs
Definition FastArraySerializer.h:374
int32 NumBufferBits
Definition FastArraySerializer.h:380
TSet< FNetworkGUID > UnmappedGUIDs
Definition FastArraySerializer.h:371
Definition FastArraySerializer.h:299
FString GetDebugString()
Definition FastArraySerializer.h:364
void PostReplicatedAdd(const struct FFastArraySerializer &InArraySerializer)
Definition FastArraySerializer.h:349
int32 ReplicationKey
Definition FastArraySerializer.h:329
FFastArraySerializerItem(const FFastArraySerializerItem &InItem)
Definition FastArraySerializer.h:308
FFastArraySerializerItem & operator=(const FFastArraySerializerItem &In)
Definition FastArraySerializer.h:314
int32 ReplicationID
Definition FastArraySerializer.h:326
void PreReplicatedRemove(const struct FFastArraySerializer &InArraySerializer)
Definition FastArraySerializer.h:341
void PostReplicatedChange(const struct FFastArraySerializer &InArraySerializer)
Definition FastArraySerializer.h:356
Definition FastArraySerializer.h:534
auto Requires(InFastArrayType *FastArray, const FFastArraySerializer::FPostReplicatedReceiveParameters &Parameters) -> decltype(FastArray->PostReplicatedReceive(Parameters))
Definition FastArraySerializer.h:522
int32 OldArraySize
Definition FastArraySerializer.h:524
int32 ID
Definition FastArraySerializer.h:391
FFastArraySerializer_FastArrayDeltaSerialize_FIdxIDPair(int32 _idx, int32 _id)
Definition FastArraySerializer.h:389
int32 Idx
Definition FastArraySerializer.h:390
Definition FastArraySerializer.h:409
const EFastArraySerializerDeltaFlags GetDeltaSerializationFlags() const
Definition FastArraySerializer.h:568
static const int32 GetMaxNumberOfAllowedDeletionsPerUpdate()
Definition FastArraySerializer.h:578
void SetDeltaSerializationEnabled(const bool bEnabled)
Definition FastArraySerializer.h:549
void MarkItemDirty(FFastArraySerializerItem &Item)
Definition FastArraySerializer.h:441
void PostReplicatedChange(const TArrayView< int32 > &ChangedIndices, int32 FinalSize)
Definition FastArraySerializer.h:515
NETCORE_API ~FFastArraySerializer()
void PreReplicatedRemove(const TArrayView< int32 > &RemovedIndices, int32 FinalSize)
Definition FastArraySerializer.h:501
TMap< int32, int32 > ItemMap
Definition FastArraySerializer.h:416
void MarkArrayDirty()
Definition FastArraySerializer.h:457
bool ShouldWriteFastArrayItem(const Type &Item, const bool bIsWritingOnClient) const
Definition FastArraySerializer.h:544
static const int32 GetMaxNumberOfAllowedChangesPerUpdate()
Definition FastArraySerializer.h:573
void IncrementArrayReplicationKey()
Definition FastArraySerializer.h:467
static bool FastArrayDeltaSerialize(TArray< Type > &Items, FNetDeltaSerializeInfo &Parms, SerializerType &ArraySerializer)
Definition FastArraySerializer.h:1207
void PostReplicatedAdd(const TArrayView< int32 > &AddedIndices, int32 FinalSize)
Definition FastArraySerializer.h:508
FString ToString(EGuidFormats Format=EGuidFormats::Digits) const
Definition Guid.h:329
Definition CoreNet.h:643
class UStruct * Struct
Definition CoreNet.h:666
bool bCalledPreNetReceive
Definition CoreNet.h:678
TSet< FNetworkGUID > * GatherGuidReferences
Definition CoreNet.h:720
int32 * TrackedGuidMemoryBytes
Definition CoreNet.h:727
FBitWriter * Writer
Definition CoreNet.h:645
bool bSupportsFastArrayDeltaStructSerialization
Definition CoreNet.h:696
const FNetworkGUID * MoveGuidToUnmapped
Definition CoreNet.h:730
UObject * Object
Definition CoreNet.h:710
bool bOutSomeObjectsWereMapped
Definition CoreNet.h:675
bool bGuidListsChanged
Definition CoreNet.h:684
void * Data
Definition CoreNet.h:663
FBitReader * Reader
Definition CoreNet.h:648
class UPackageMap * Map
Definition CoreNet.h:657
bool bOutHasMoreUnmapped
Definition CoreNet.h:681
bool bIsInitializingBaseFromDefault
Definition CoreNet.h:693
bool bUpdateUnmappedObjects
Definition CoreNet.h:672
INetSerializeCB * NetSerializeCB
Definition CoreNet.h:669
INetDeltaBaseState * OldState
Definition CoreNet.h:654
TSharedPtr< INetDeltaBaseState > * NewState
Definition CoreNet.h:651
Definition UnrealTypeTraits.h:40
Definition NumericLimits.h:41
Definition UnrealTemplate.h:511
Definition StructOpsTypeTraits.h:11
@ WithNetDeltaSerializer
Definition StructOpsTypeTraits.h:27
Definition StructOpsTypeTraits.h:46
Definition Tuple.h:652