// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= UnCoreNet.cpp: Core networking support. =============================================================================*/ #include "UObject/CoreNet.h" #include "UObject/UnrealType.h" #include "Misc/NetworkVersion.h" DEFINE_LOG_CATEGORY_STATIC(LogCoreNet, Log, All); /*----------------------------------------------------------------------------- FClassNetCache implementation. -----------------------------------------------------------------------------*/ FClassNetCache::FClassNetCache() { } FClassNetCache::FClassNetCache(const UClass* InClass): Class(InClass) { } void FClassNetCacheMgr::SortProperties(TArray& Properties) const { // Sort NetProperties so that their ClassReps are sorted by memory offset struct FCompareUFieldOffsets { FORCEINLINE bool operator()(FProperty& A, FProperty& B) const { // Ensure stable sort if (A.GetOffset_ForGC() == B.GetOffset_ForGC()) { return A.GetName() < B.GetName(); } return A.GetOffset_ForGC() < B.GetOffset_ForGC(); } }; Sort(Properties.GetData(), Properties.Num(), FCompareUFieldOffsets()); } uint32 FClassNetCacheMgr::SortedStructFieldsChecksum(const UStruct* Struct, uint32 Checksum) const { // Generate a list that we can sort, to make sure we process these deterministically TArray Fields; for (TFieldIterator It(Struct); It; ++It) { if (It->PropertyFlags & CPF_RepSkip) { continue; } Fields.Add(*It); } // Sort them SortProperties(Fields); // Evolve the checksum on the sorted list for (auto Field: Fields) { Checksum = GetPropertyChecksum(Field, Checksum, true); } return Checksum; } uint32 FClassNetCacheMgr::GetPropertyChecksum(const FProperty* Property, uint32 Checksum, const bool bIncludeChildren) const { if (bDebugChecksum) { UE_LOG(LogCoreNet, Warning, TEXT("%s%s [%s] [%u] [%u]"), FCString::Spc(2 * DebugChecksumIndent), *Property->GetName().ToLower(), *Property->GetClass()->GetName().ToLower(), Property->ArrayDim, Checksum); } Checksum = FCrc::StrCrc32(*Property->GetName().ToLower(), Checksum); // Evolve checksum on name Checksum = FCrc::StrCrc32(*Property->GetCPPType(nullptr, 0).ToLower(), Checksum); // Evolve by property type Checksum = FCrc::StrCrc32(*FString::Printf(TEXT("%u"), Property->ArrayDim), Checksum); // Evolve checksum on array dim (to detect when static arrays change size) if (bIncludeChildren) { const FArrayProperty* ArrayProperty = CastField(Property); // Evolve checksum on array inner if (ArrayProperty != NULL) { return GetPropertyChecksum(ArrayProperty->Inner, Checksum, bIncludeChildren); } const FStructProperty* StructProperty = CastField(Property); // Evolve checksum on property struct fields if (StructProperty != NULL) { if (bDebugChecksum) { UE_LOG(LogCoreNet, Warning, TEXT("%s [%s] [%u]"), FCString::Spc(2 * DebugChecksumIndent), *StructProperty->Struct->GetName().ToLower(), Checksum); } // Evolve checksum on struct name Checksum = FCrc::StrCrc32(*StructProperty->Struct->GetName().ToLower(), Checksum); const_cast(this)->DebugChecksumIndent++; Checksum = SortedStructFieldsChecksum(StructProperty->Struct, Checksum); const_cast(this)->DebugChecksumIndent--; } } return Checksum; } uint32 FClassNetCacheMgr::GetFunctionChecksum(const UFunction* Function, uint32 Checksum) const { // Evolve checksum on function name Checksum = FCrc::StrCrc32(*Function->GetName().ToLower(), Checksum); // Evolve the checksum on function flags Checksum = FCrc::StrCrc32(*FString::Printf(TEXT("%u"), (uint32)Function->FunctionFlags), Checksum); #if 0 // This is disabled now that we have backwards compatibility for RPC parameters working in replays TArray< FProperty * > Parms; for ( TFieldIterator< FProperty > It( Function ); It && ( It->PropertyFlags & ( CPF_Parm | CPF_ReturnParm ) ) == CPF_Parm; ++It ) { Parms.Add( *It ); } // Sort parameters by offset/name SortProperties( Parms ); // Evolve checksum on sorted function parameters for ( FProperty* Parm : Parms ) { Checksum = GetPropertyChecksum( Parm, Checksum, true ); } #endif return Checksum; } uint32 FClassNetCacheMgr::GetFieldChecksum(const UField* Field, uint32 Checksum) const { if (Cast(Field) != NULL) { return GetFunctionChecksum((UFunction*)Field, Checksum); } UE_LOG(LogCoreNet, Warning, TEXT("GetFieldChecksum: Unknown field: %s"), *Field->GetName()); return Checksum; } const FClassNetCache* FClassNetCacheMgr::GetClassNetCache(UClass* Class) { FClassNetCache* Result = ClassFieldIndices.FindRef(Class); if (!Result) { Class->SetUpRuntimeReplicationData(); Result = ClassFieldIndices.Add(Class, new FClassNetCache(Class)); Result->Super = NULL; Result->FieldsBase = 0; Result->ClassChecksum = 0; if (Class->GetSuperClass()) { Result->Super = GetClassNetCache(Class->GetSuperClass()); Result->FieldsBase = Result->Super->GetMaxIndex(); Result->ClassChecksum = Result->Super->ClassChecksum; } Result->Fields.Empty(Class->NetFields.Num()); TArray Properties; Properties.Empty(Class->ClassReps.Num() - Class->FirstOwnedClassRep); for (int32 i = Class->FirstOwnedClassRep; i < Class->ClassReps.Num(); i++) { // Add each net field to cache, and assign index/checksum FProperty* Property = Class->ClassReps[i].Property; check(Property); Properties.Add(Property); // Get individual checksum const uint32 Checksum = GetPropertyChecksum(Property, 0, false); // Get index const int32 ThisIndex = Result->GetMaxIndex(); // Add to cached fields on this class Result->Fields.Add(FFieldNetCache(Property, ThisIndex, Checksum)); // Skip over static array properties. i += (Property->ArrayDim - 1); } for (int32 i = 0; i < Class->NetFields.Num(); i++) { // Add each net field to cache, and assign index/checksum UField* Field = Class->NetFields[i]; // Get individual checksum const uint32 Checksum = GetFieldChecksum(Field, 0); // Get index const int32 ThisIndex = Result->GetMaxIndex(); // Add to cached fields on this class Result->Fields.Add(FFieldNetCache(Field, ThisIndex, Checksum)); } Result->Fields.Shrink(); // Add fields to the appropriate hash maps for (TArray::TIterator It(Result->Fields); It; ++It) { Result->FieldMap.Add(It->Field.GetRawPointer(), &*It); if (Result->FieldChecksumMap.Contains(It->FieldChecksum)) { UE_LOG(LogCoreNet, Error, TEXT("Duplicate checksum: %s, %u"), *It->Field.GetFullName(), It->FieldChecksum); } Result->FieldChecksumMap.Add(It->FieldChecksum, &*It); } // Initialize class checksum (just use properties for this) SortProperties(Properties); for (auto Property: Properties) { Result->ClassChecksum = GetPropertyChecksum(Property, Result->ClassChecksum, true); } } return Result; } void FClassNetCacheMgr::ClearClassNetCache() { for (auto It = ClassFieldIndices.CreateIterator(); It; ++It) { delete It.Value(); } ClassFieldIndices.Empty(); } void FClassNetCacheMgr::CountBytes(FArchive& Ar) const { ClassFieldIndices.CountBytes(Ar); for (const auto& ClassFieldPair: ClassFieldIndices) { if (ClassFieldPair.Value != nullptr) { Ar.CountBytes(sizeof(FClassNetCache), sizeof(FClassNetCache)); ClassFieldPair.Value->CountBytes(Ar); } } } void FClassNetCache::CountBytes(FArchive& Ar) const { Fields.CountBytes(Ar); FieldMap.CountBytes(Ar); FieldChecksumMap.CountBytes(Ar); } /*----------------------------------------------------------------------------- UPackageMap implementation. -----------------------------------------------------------------------------*/ bool UPackageMap::SerializeName(FArchive& Ar, FName& InName) { return StaticSerializeName(Ar, InName); } bool UPackageMap::StaticSerializeName(FArchive& Ar, FName& InName) { if (Ar.IsLoading()) { uint8 bHardcoded = 0; Ar.SerializeBits(&bHardcoded, 1); if (bHardcoded) { // replicated by hardcoded index uint32 NameIndex; if (Ar.EngineNetVer() < HISTORY_CHANNEL_NAMES) { Ar.SerializeInt(NameIndex, MAX_NETWORKED_HARDCODED_NAME + 1); } else { Ar.SerializeIntPacked(NameIndex); } if (NameIndex < NAME_MaxHardcodedNameIndex) { InName = EName(NameIndex); // hardcoded names never have a Number } else { Ar.SetError(); } } else { // replicated by string FString InString; int32 InNumber; Ar << InString << InNumber; InName = FName(*InString, InNumber); } } else if (Ar.IsSaving()) { const EName* InEName = InName.ToEName(); uint8 bHardcoded = InEName && ShouldReplicateAsInteger(*InEName); Ar.SerializeBits(&bHardcoded, 1); if (bHardcoded && /* silence static analyzer */ InEName) { // send by hardcoded index checkSlow(InName.GetNumber() <= 0); // hardcoded names should never have a Number uint32 NameIndex = *InEName; Ar.SerializeIntPacked(NameIndex); } else { // send by string FString OutString = InName.GetPlainNameString(); int32 OutNumber = InName.GetNumber(); Ar << OutString << OutNumber; } } return true; } IMPLEMENT_CORE_INTRINSIC_CLASS(UPackageMap, UObject, {}); void UPackageMap::Serialize(FArchive& Ar) { Super::Serialize(Ar); if (Ar.IsCountingMemory()) { TrackedUnmappedNetGuids.CountBytes(Ar); TrackedMappedDynamicNetGuids.CountBytes(Ar); Ar << DebugContextString; } } // ---------------------------------------------------------------- void SerializeChecksum(FArchive& Ar, uint32 x, bool ErrorOK) { if (Ar.IsLoading()) { uint32 Magic = 0; Ar << Magic; if ((!ErrorOK || !Ar.IsError()) && !ensure(Magic == x)) { UE_LOG(LogCoreNet, Warning, TEXT("%d == %d"), Magic, x); } } else { uint32 Magic = x; Ar << Magic; } } void FPropertyRetirement::CountBytes(FArchive& Ar) const { for (const FPropertyRetirement* NextRetirement = Next; NextRetirement; NextRetirement = NextRetirement->Next) { Ar.CountBytes(sizeof(FPropertyRetirement), sizeof(FPropertyRetirement)); } } // ---------------------------------------------------------------- // FNetBitWriter // ---------------------------------------------------------------- FNetBitWriter::FNetBitWriter() : FBitWriter(0), PackageMap(nullptr) {} FNetBitWriter::FNetBitWriter(int64 InMaxBits) : FBitWriter(InMaxBits, true), PackageMap(nullptr) { } FNetBitWriter::FNetBitWriter(UPackageMap* InPackageMap, int64 InMaxBits) : FBitWriter(InMaxBits, true), PackageMap(InPackageMap) { } FArchive& FNetBitWriter::operator<<(class FName& N) { if (PackageMap) { PackageMap->SerializeName(*this, N); } else { UPackageMap::StaticSerializeName(*this, N); } return *this; } FArchive& FNetBitWriter::operator<<(UObject*& Object) { PackageMap->SerializeObject(*this, UObject::StaticClass(), Object); return *this; } FArchive& FNetBitWriter::operator<<(FSoftObjectPath& Value) { // It's more efficient to serialize as a string then name+string FString Path = Value.ToString(); *this << Path; return *this; } FArchive& FNetBitWriter::operator<<(FSoftObjectPtr& Value) { return FArchiveUObject::SerializeSoftObjectPtr(*this, Value); } FArchive& FNetBitWriter::operator<<(struct FWeakObjectPtr& WeakObjectPtr) { return FArchiveUObject::SerializeWeakObjectPtr(*this, WeakObjectPtr); } void FNetBitWriter::CountMemory(FArchive& Ar) const { FBitWriter::CountMemory(Ar); const SIZE_T MemberSize = sizeof(*this) - sizeof(FBitWriter); Ar.CountBytes(MemberSize, MemberSize); } // ---------------------------------------------------------------- // FNetBitReader // ---------------------------------------------------------------- FNetBitReader::FNetBitReader(UPackageMap* InPackageMap, uint8* Src, int64 CountBits) : FBitReader(Src, CountBits), PackageMap(InPackageMap) { } FArchive& FNetBitReader::operator<<(UObject*& Object) { PackageMap->SerializeObject(*this, UObject::StaticClass(), Object); return *this; } FArchive& FNetBitReader::operator<<(class FName& N) { if (PackageMap) { PackageMap->SerializeName(*this, N); } else { UPackageMap::StaticSerializeName(*this, N); } return *this; } FArchive& FNetBitReader::operator<<(FSoftObjectPath& Value) { FString Path; *this << Path; Value.SetPath(MoveTemp(Path)); return *this; } FArchive& FNetBitReader::operator<<(FSoftObjectPtr& Value) { return FArchiveUObject::SerializeSoftObjectPtr(*this, Value); } FArchive& FNetBitReader::operator<<(struct FWeakObjectPtr& WeakObjectPtr) { return FArchiveUObject::SerializeWeakObjectPtr(*this, WeakObjectPtr); return *this; } void FNetBitReader::CountMemory(FArchive& Ar) const { FBitReader::CountMemory(Ar); const SIZE_T MemberSize = sizeof(*this) - sizeof(FBitReader); Ar.CountBytes(MemberSize, MemberSize); } static const TCHAR* GLastRPCFailedReason = NULL; void RPC_ResetLastFailedReason() { GLastRPCFailedReason = NULL; } void RPC_ValidateFailed(const TCHAR* Reason) { GLastRPCFailedReason = Reason; } const TCHAR* RPC_GetLastFailedReason() { return GLastRPCFailedReason; } const TCHAR* LexToString(const EChannelCloseReason Value) { switch (Value) { case EChannelCloseReason::Destroyed: return TEXT("Destroyed"); case EChannelCloseReason::Dormancy: return TEXT("Dormancy"); case EChannelCloseReason::LevelUnloaded: return TEXT("LevelUnloaded"); case EChannelCloseReason::Relevancy: return TEXT("Relevancy"); case EChannelCloseReason::TearOff: return TEXT("TearOff"); } return TEXT("Unknown"); } void INetSerializeCB::NetSerializeStruct( class UScriptStruct* Struct, class FBitArchive& Ar, class UPackageMap* Map, void* Data, bool& bHasUnmapped) { FNetDeltaSerializeInfo Params; Params.Struct = Struct; Params.Map = Map; Params.Data = Data; if (Ar.IsSaving()) { Params.Writer = static_cast(&Ar); } else { Params.Reader = static_cast(&Ar); } NetSerializeStruct(Params); bHasUnmapped = Params.bOutHasMoreUnmapped; }