// Copyright Epic Games, Inc. All Rights Reserved. #include "UniversalObjectLocatorFragment.h" #include "Algo/Find.h" #include "UniversalObjectLocatorFragmentType.h" #include "UniversalObjectLocatorStringParams.h" #include "UniversalObjectLocatorInitializeParams.h" #include "UniversalObjectLocatorInitializeResult.h" #include "UniversalObjectLocatorFragmentDebugging.h" #include "UniversalObjectLocatorRegistry.h" #include "UObject/SoftObjectPath.h" #include "UObject/UObjectThreadContext.h" #include "Containers/SparseArray.h" #include "Logging/MessageLog.h" #include "Misc/AsciiSet.h" #include "Misc/UObjectToken.h" #include "Templates/AlignmentTemplates.h" #define LOCTEXT_NAMESPACE "UOL" DECLARE_LOG_CATEGORY_EXTERN(LogUniversalObjectLocator, Log, Log); DEFINE_LOG_CATEGORY(LogUniversalObjectLocator); namespace UE::UniversalObjectLocator { const FFragmentType* FindBestFragmentType(const UObject* Object, UObject* Context) { // Loop through all our FragmentTypes to find the most supported one uint32 BestFragmentTypePriority = 0; const FFragmentType* BestFragmentType = nullptr; for (const FFragmentType& FragmentType : FRegistry::Get().FragmentTypes) { const uint32 ThisFragmentTypePriority = FragmentType.ComputePriority(Object, Context); if (ThisFragmentTypePriority > BestFragmentTypePriority) { BestFragmentTypePriority = ThisFragmentTypePriority; BestFragmentType = &FragmentType; } } if (BestFragmentType && BestFragmentType->PayloadType != nullptr) { return BestFragmentType; } return nullptr; } FFragmentType* FFragmentTypeHandle::Resolve() const { return Handle == 0xff ? nullptr : &FRegistry::Get().FragmentTypes[Handle]; } FFragmentTypeHandle MakeFragmentTypeHandle(const FFragmentType* FragmentType) { check(FragmentType); const uint64 FragmentTypeOffset = static_cast(FragmentType - FRegistry::Get().FragmentTypes.GetData()); checkf(FragmentTypeOffset < std::numeric_limits::max(), TEXT("Maximum number of UOL FragmentTypes reached")); return FFragmentTypeHandle(static_cast(FragmentTypeOffset)); } /** * Compute the size required for the debug header of a fragment with a certain size and alignment constraint * * Since our byte array is explicitly aligned to 8 bytes, we can insert the debug header right at the start of * our bytes without changing the alignment of the proceeding type. * * If however our type's requested alignment is greater, we use the alignment itself and allocate the header at * the tail of that space. For instance, for a 16 byte aligned payload: * 0.. 8.. 16.. 16 + sizeof(T) * [ TFragmentPayload | T Payload ] * * For a 32 byte aligned payload: * 0.. 24.. 32.. 32 + sizeof(T) * [ TFragmentPayload | T Payload ] * */ uint8 ComputeDebugHeaderLog2(size_t Alignment) { #if UE_UNIVERSALOBJECTLOCATOR_DEBUG static_assert(alignof(IFragmentPayload) == 8 && sizeof(IFragmentPayload) == 8, "Unexpected alignment/size of IFragmentPayload!"); const uint32 HeaderCapacityLog2 = FMath::CeilLogTwo((uint32)FMath::Max(Alignment, sizeof(IFragmentPayload))); // This value is stored in 6 bits of a uint8 // We should never encounter a type aligned > 2^63! check(HeaderCapacityLog2 <= 63); return static_cast(HeaderCapacityLog2); #else return 0; #endif } } // UE::UniversalObjectLocator FUniversalObjectLocatorFragment::FUniversalObjectLocatorFragment(const UObject* InObject, UObject* Context) : bIsInitialized(0) , bIsInline(0) , DebugHeaderSizeLog2(0) { Reset(InObject, Context); } FUniversalObjectLocatorFragment::FUniversalObjectLocatorFragment(const UE::UniversalObjectLocator::FFragmentType& InFragmentType) : FragmentType(MakeFragmentTypeHandle(&InFragmentType)) , bIsInitialized(0) , bIsInline(0) , DebugHeaderSizeLog2(0) { this->DefaultConstructPayload(InFragmentType); } FUniversalObjectLocatorFragment::FUniversalObjectLocatorFragment() : bIsInitialized(0) , bIsInline(0) , DebugHeaderSizeLog2(0) { static_assert(sizeof(FUniversalObjectLocatorFragment) == FUniversalObjectLocatorFragment::SizeInMemory, "Unexpected size for FUniversalObjectLocatorFragment"); static_assert(offsetof(FUniversalObjectLocatorFragment, Data) == 0, "FUniversalObjectLocatorFragment inline data is not aligned properly"); } FUniversalObjectLocatorFragment::~FUniversalObjectLocatorFragment() { if (bIsInitialized && !IsEngineExitRequested()) { DestroyPayload(); } } FUniversalObjectLocatorFragment::FUniversalObjectLocatorFragment(const FUniversalObjectLocatorFragment& RHS) : FragmentType(RHS.FragmentType) , bIsInitialized(0) , bIsInline(0) , DebugHeaderSizeLog2(0) { using namespace UE::UniversalObjectLocator; if (RHS.bIsInitialized) { const FFragmentType* ResolvedFragmentType = GetFragmentType(); check(ResolvedFragmentType); this->DefaultConstructPayload(*ResolvedFragmentType); ResolvedFragmentType->PayloadType->CopyScriptStruct(this->GetPayload(), RHS.GetPayload()); } else { this->bIsInitialized = false; } } FUniversalObjectLocatorFragment& FUniversalObjectLocatorFragment::operator=(const FUniversalObjectLocatorFragment& RHS) { using namespace UE::UniversalObjectLocator; this->DestroyPayload(); if (RHS.bIsInitialized) { const FFragmentType* ResolvedFragmentType = GetFragmentType(); check(ResolvedFragmentType); // Assign the FragmentType and copy the payload this->FragmentType = RHS.FragmentType; this->DefaultConstructPayload(*ResolvedFragmentType); ResolvedFragmentType->PayloadType->CopyScriptStruct(this->GetPayload(), RHS.GetPayload()); } else { this->bIsInitialized = false; this->FragmentType = FFragmentTypeHandle(); } return *this; } FUniversalObjectLocatorFragment::FUniversalObjectLocatorFragment(FUniversalObjectLocatorFragment&& RHS) : FragmentType(RHS.FragmentType) , bIsInitialized(RHS.bIsInitialized) , bIsInline(RHS.bIsInline) , DebugHeaderSizeLog2(RHS.DebugHeaderSizeLog2) { using namespace UE::UniversalObjectLocator; FMemory::Memcpy(this->Data, RHS.Data, sizeof(Data)); RHS.bIsInitialized = false; RHS.bIsInline = false; RHS.DebugHeaderSizeLog2 = 0; RHS.FragmentType = FFragmentTypeHandle(); } FUniversalObjectLocatorFragment& FUniversalObjectLocatorFragment::operator=(FUniversalObjectLocatorFragment&& RHS) { using namespace UE::UniversalObjectLocator; this->DestroyPayload(); this->bIsInitialized = RHS.bIsInitialized; this->bIsInline = RHS.bIsInline; this->DebugHeaderSizeLog2 = RHS.DebugHeaderSizeLog2; this->FragmentType = RHS.FragmentType; FMemory::Memcpy(this->Data, RHS.Data, sizeof(Data)); RHS.bIsInitialized = false; RHS.bIsInline = false; RHS.DebugHeaderSizeLog2 = 0; RHS.FragmentType = FFragmentTypeHandle(); return *this; } bool operator==(const FUniversalObjectLocatorFragment& A, const FUniversalObjectLocatorFragment& B) { using namespace UE::UniversalObjectLocator; if (A.bIsInitialized != B.bIsInitialized) { return false; } else if (!A.bIsInitialized) { // 2 uninitialized references are the same return true; } else if (A.FragmentType != B.FragmentType) { // Different fragment types return false; } else { const UScriptStruct* FragmentStruct = A.GetFragmentStruct(); check(FragmentStruct); // Same fragment types - compare payloads const void* PayloadA = A.GetPayload(); const void* PayloadB = B.GetPayload(); return FragmentStruct->CompareScriptStruct(PayloadA, PayloadB, 0); } } bool operator!=(const FUniversalObjectLocatorFragment& A, const FUniversalObjectLocatorFragment& B) { return !(A == B); } uint32 GetTypeHash(const FUniversalObjectLocatorFragment& Fragment) { using namespace UE::UniversalObjectLocator; if (!Fragment.bIsInitialized) { return 0; } uint32 Hash = GetTypeHash(Fragment.FragmentType); if (const FFragmentType* FragmentTypePtr = Fragment.GetFragmentType()) { UScriptStruct* Struct = FragmentTypePtr->GetStruct(); if (Struct) { const uint32 PayloadHash = Struct->GetStructTypeHash(Fragment.GetPayload()); Hash = HashCombineFast(Hash, PayloadHash); } } return Hash; } #if DO_CHECK void FUniversalObjectLocatorFragment::CheckPayloadType(UScriptStruct* TypeToCompare) const { using namespace UE::UniversalObjectLocator; const FFragmentType* FragmentTypePtr = GetFragmentType(); checkf(FragmentTypePtr == nullptr || FragmentTypePtr->PayloadType == TypeToCompare, TEXT("Type mismatch when accessing payload data! Attempting to access a stored %s payload as %s."), FragmentTypePtr->PayloadType ? *FragmentTypePtr->PayloadType->GetName() : TEXT(""), TypeToCompare ? *TypeToCompare->GetName() : TEXT("")); } #endif void FUniversalObjectLocatorFragment::ToString(FStringBuilderBase& OutString) const { using namespace UE::UniversalObjectLocator; const FFragmentType* FragmentTypePtr = FragmentType.Resolve(); if (FragmentTypePtr && FragmentTypePtr->PayloadType) { FragmentTypePtr->FragmentTypeID.AppendString(OutString); TStringBuilder<128> PayloadString; FragmentTypePtr->ToString(GetPayload(), PayloadString); if (PayloadString.Len() != 0) { OutString += '='; OutString.Append(PayloadString.ToView()); } } } UE::UniversalObjectLocator::FParseStringResult FUniversalObjectLocatorFragment::TryParseString(FStringView InString, const FParseStringParams& InParams) { using namespace UE::UniversalObjectLocator; if (InString.Len() == 0) { Reset(); return FParseStringResult().Success(); } // Check for a literal "none" text static constexpr FStringView NoneString = TEXTVIEW("none"); if (InString.Compare(NoneString, ESearchCase::IgnoreCase) == 0) { Reset(); return FParseStringResult().Success(NoneString.Len()); } FStringView FragmentTypeString = InString; FStringView FragmentPayloadString; const int32 Delimiter = UE::String::FindFirstChar(InString, '='); if (Delimiter != INDEX_NONE) { // We have a payload FragmentTypeString = InString.Left(Delimiter); FragmentPayloadString = InString.RightChop(Delimiter + 1); if (FragmentTypeString.Len() == 0) { return FParseStringResult().Failure(UE_UOL_PARSE_ERROR(InParams, LOCTEXT("Error_UnexpectedEquals", "Unexpected '='' when expecting a fragment type."))); } } FParseStringResult TypeResult = TryParseFragmentType(FragmentTypeString, InParams); if (!TypeResult) { return TypeResult; } FParseStringResult PayloadResult = TryParseFragmentPayload(FragmentPayloadString, InParams); // Add the type chars and = to the total num parsed PayloadResult.NumCharsParsed += TypeResult.NumCharsParsed + 1; return PayloadResult; } UE::UniversalObjectLocator::FParseStringResult FUniversalObjectLocatorFragment::TryParseFragmentType(FStringView InString, const FParseStringParams& InParams) { using namespace UE::UniversalObjectLocator; if (InString.Len() == 0) { return FParseStringResult().Failure(UE_UOL_PARSE_ERROR(InParams, LOCTEXT("Error_EmptyFragmentType", "Fragment type specifier is empty."))); } // Check for a literal "none" text static constexpr FStringView NoneString = TEXTVIEW("none"); if (InString.Compare(NoneString, ESearchCase::IgnoreCase) == 0) { Reset(); return FParseStringResult().Success(NoneString.Len()); } // Try and find the FragmentType as a name FName FragmentTypeID(InString.Len(), InString.GetData(), FNAME_Find); if (FragmentTypeID != NAME_None) { // Find the FragmentType const FFragmentType* SerializedFragmentType = FRegistry::Get().FindFragmentType(FragmentTypeID); if (SerializedFragmentType != nullptr && SerializedFragmentType->PayloadType != nullptr) { this->DestroyPayload(); this->FragmentType = MakeFragmentTypeHandle(SerializedFragmentType); this->DefaultConstructPayload(*SerializedFragmentType); return FParseStringResult().Success(InString.Len()); } } // Not a valid fragment type string return FParseStringResult().Failure( UE_UOL_PARSE_ERROR(InParams, FText::Format( LOCTEXT("Error_UnknownFragmentType", "Unknown fragment type specifier {0}."), FText::FromStringView(InString) ) ) ); } UE::UniversalObjectLocator::FParseStringResult FUniversalObjectLocatorFragment::TryParseFragmentPayload(FStringView InString, const FParseStringParams& InParams) { using namespace UE::UniversalObjectLocator; if (!bIsInitialized) { return FParseStringResult().Failure(UE_UOL_PARSE_ERROR(InParams, LOCTEXT("Error_Uninitialized", "Unable to parse a payload for an uninitialized fragment."))); } const FFragmentType* FragmentTypePtr = GetFragmentType(); const UScriptStruct* FragmentStruct = FragmentTypePtr ? FragmentTypePtr->GetStruct() : nullptr; if (!FragmentStruct) { return FParseStringResult().Failure(UE_UOL_PARSE_ERROR(InParams, LOCTEXT("Error_Expired", "Unable to parse a payload for a fragment whose type has expired."))); } void* Payload = GetPayload(); if (InString.Len() == 0) { // Empty payload string means a default payload FragmentStruct->ClearScriptStruct(Payload); return FParseStringResult().Success(); } return FragmentTypePtr->TryParseString(Payload, InString, InParams); } const UE::UniversalObjectLocator::FFragmentType* FUniversalObjectLocatorFragment::GetFragmentType() const { return FragmentType.Resolve(); } UScriptStruct* FUniversalObjectLocatorFragment::GetFragmentStruct() const { using namespace UE::UniversalObjectLocator; if (const FFragmentType* Type = GetFragmentType()) { return Type->GetStruct(); } return nullptr; } UE::UniversalObjectLocator::FFragmentTypeHandle FUniversalObjectLocatorFragment::GetFragmentTypeHandle() const { using namespace UE::UniversalObjectLocator; const FFragmentType* FragmentTypePtr = GetFragmentType(); if (FragmentTypePtr) { return MakeFragmentTypeHandle(FragmentTypePtr); } return FFragmentTypeHandle(); } void FUniversalObjectLocatorFragment::DestroyPayload() { using namespace UE::UniversalObjectLocator; if (!bIsInitialized) { return; } uint8* Payload = (uint8*)GetPayload(); const UScriptStruct* FragmentStruct = GetFragmentStruct(); if (ensureMsgf(FragmentStruct, TEXT("FUniversalObjectLocatorFragment has outlived its FragmentType's payload type struct! This could leak memory if the type allocated it."))) { FragmentStruct->DestroyStruct(Payload); } if (!bIsInline) { Payload -= GetDebugHeaderOffset(); FMemory::Free(Payload); } bIsInitialized = false; } void* FUniversalObjectLocatorFragment::GetPayload() { check(bIsInitialized); uint8* Payload = bIsInline ? Data : *((uint8**)Data); return Payload + GetDebugHeaderOffset(); } const void* FUniversalObjectLocatorFragment::GetPayload() const { check(bIsInitialized); const uint8* Payload = bIsInline ? Data : *((const uint8* const *)Data); return Payload + GetDebugHeaderOffset(); } FUniversalObjectLocatorFragment::FAllocatedPayload FUniversalObjectLocatorFragment::AllocatePayload(size_t Size, size_t Alignment) { using namespace UE::UniversalObjectLocator; check(!bIsInitialized); bIsInitialized = true; #if UE_UNIVERSALOBJECTLOCATOR_DEBUG DebugHeaderSizeLog2 = ComputeDebugHeaderLog2(Alignment); Alignment = FMath::Max(Alignment, alignof(IFragmentPayload)); Size += GetDebugHeaderOffset(); #else DebugHeaderSizeLog2 = 0; #endif uint8* Payload = nullptr; if (Size <= sizeof(FUniversalObjectLocatorFragment::Data) && Alignment <= alignof(FUniversalObjectLocatorFragment)) { // We can placement new this into the payload data bIsInline = true; Payload = Data; } else { // We have to allocate this struct on the heap Payload = (uint8*)FMemory::Malloc(Size, Alignment); *reinterpret_cast(Data) = Payload; bIsInline = false; } return FAllocatedPayload{ #if UE_UNIVERSALOBJECTLOCATOR_DEBUG Payload + GetDebugHeaderOffset() - sizeof(IFragmentPayload), #endif Payload + GetDebugHeaderOffset() }; } void FUniversalObjectLocatorFragment::DefaultConstructPayload(const UE::UniversalObjectLocator::FFragmentType& InFragmentType) { using namespace UE::UniversalObjectLocator; const UScriptStruct* PayloadType = InFragmentType.PayloadType.Get(); FAllocatedPayload Allocation = AllocatePayload((size_t)PayloadType->GetStructureSize(), (size_t)PayloadType->GetMinAlignment()); #if UE_UNIVERSALOBJECTLOCATOR_DEBUG InFragmentType.StaticBindings.FragmentDebugInitializer(Allocation.DebugVFTablePtr); #endif PayloadType->InitializeStruct(Allocation.Payload); } void FUniversalObjectLocatorFragment::Reset() { using namespace UE::UniversalObjectLocator; DestroyPayload(); FragmentType = FFragmentTypeHandle(); } void FUniversalObjectLocatorFragment::Reset(const UObject* InObject, UObject* Context) { using namespace UE::UniversalObjectLocator; Reset(); if (const FFragmentType* BestFragmentType = FindBestFragmentType(InObject, Context)) { FragmentType = MakeFragmentTypeHandle(BestFragmentType); DefaultConstructPayload(*BestFragmentType); BestFragmentType->InitializePayload(GetPayload(), FInitializeParams{ InObject, Context }); } } void FUniversalObjectLocatorFragment::Reset(const UObject* InObject, UObject* Context, TFunctionRef CanUseFragmentType) { using namespace UE::UniversalObjectLocator; Reset(); // Loop through all our FragmentTypes to find the most supported one uint32 BestFragmentTypePriority = 0; const FFragmentType* BestFragmentType = nullptr; TArray& FragmentTypes = FRegistry::Get().FragmentTypes; if (!ensure(FragmentTypes.Num() < 255)) { return; } const uint8 Num = static_cast(FragmentTypes.Num()); for (uint8 Index = 0; Index < Num; ++Index) { if (!CanUseFragmentType(FFragmentTypeHandle(Index))) { continue; } const FFragmentType& ThisFragmentType = FragmentTypes[Index]; const uint32 ThisFragmentTypePriority = ThisFragmentType.ComputePriority(InObject, Context); if (ThisFragmentTypePriority > BestFragmentTypePriority) { BestFragmentTypePriority = ThisFragmentTypePriority; BestFragmentType = &ThisFragmentType; } } if (BestFragmentType && BestFragmentType->PayloadType != nullptr) { FragmentType = MakeFragmentTypeHandle(BestFragmentType); DefaultConstructPayload(*BestFragmentType); BestFragmentType->InitializePayload(GetPayload(), FInitializeParams{ InObject, Context }); } } UE::UniversalObjectLocator::FResolveResult FUniversalObjectLocatorFragment::Resolve(const UE::UniversalObjectLocator::FResolveParams& Params) const { using namespace UE::UniversalObjectLocator; // Find our FragmentType const FFragmentType* FragmentTypePtr = FragmentType.Resolve(); if (FragmentTypePtr && bIsInitialized) { return FragmentTypePtr->ResolvePayload(GetPayload(), Params); } return FResolveResult(); } bool FUniversalObjectLocatorFragment::Serialize(FArchive& Ar) { using namespace UE::UniversalObjectLocator; if (Ar.IsLoading()) { FName FragmentTypeID; Ar << FragmentTypeID; if (FragmentTypeID == NAME_None) { Reset(); } else { // Find the FragmentType const FFragmentType* SerializedFragmentType = FRegistry::Get().FindFragmentType(FragmentTypeID); if (!SerializedFragmentType || !SerializedFragmentType->PayloadType) { Reset(); // Big error - what do we do? FMessageLog Log("UOL"); TSharedRef Message = Log.Error(FText::Format(LOCTEXT("DataLossWarning", "WARNING: POTENTIAL DATA LOSS! Universal Object Reference FragmentType {0}! This reference will be lost if re-saved."), FText::FromString(FragmentTypeID.ToString()))); FUObjectSerializeContext* SerializeContext = FUObjectThreadContext::Get().GetSerializeContext(); if (SerializeContext && SerializeContext->SerializedObject) { Message->AddToken(FUObjectToken::Create(SerializeContext->SerializedObject)); } Log.Open(EMessageSeverity::Error); // Deserialize an empty payload so we don't corrupt the serialization data FUniversalObjectLocatorEmptyPayload Empty; FUniversalObjectLocatorEmptyPayload::StaticStruct()->SerializeItem(Ar, &Empty, nullptr); return true; } FragmentType = MakeFragmentTypeHandle(SerializedFragmentType); UScriptStruct* Struct = SerializedFragmentType->GetStruct(); Ar.Preload(Struct); DefaultConstructPayload(*SerializedFragmentType); Struct->SerializeItem(Ar, GetPayload(), nullptr); } } else if (Ar.IsSaving() || Ar.IsTransacting()) { const FFragmentType* FragmentTypePtr = FragmentType.Resolve(); if (FragmentTypePtr == nullptr) { FName None; // FragmentType ID Ar << None; } else { FName FragmentTypeID = FragmentTypePtr->FragmentTypeID; // FragmentType ID Ar << FragmentTypeID; // FragmentType payload FragmentTypePtr->GetStruct()->SerializeItem(Ar, GetPayload(), nullptr); } } else if (Ar.IsModifyingWeakAndStrongReferences()) { UScriptStruct* FragmentStruct = GetFragmentStruct(); if (FragmentStruct && bIsInitialized) { FragmentStruct->SerializeItem(Ar, GetPayload(), nullptr); } } return true; } void FUniversalObjectLocatorFragment::AddStructReferencedObjects(FReferenceCollector& Collector) { using namespace UE::UniversalObjectLocator; if (FFragmentType* FragmentTypePtr = FragmentType.Resolve()) { Collector.AddReferencedObject(FragmentTypePtr->PayloadType); if (bIsInitialized && FragmentTypePtr->PayloadType) { Collector.AddReferencedObjects(FragmentTypePtr->PayloadType, GetPayload()); } } } bool FUniversalObjectLocatorFragment::ExportTextItem(FString& ValueStr, const FUniversalObjectLocatorFragment& DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const { TStringBuilder<32> PayloadString; ToString(PayloadString); ValueStr.AppendChar('('); ValueStr.Append(PayloadString.ToString(), PayloadString.Len()); ValueStr.AppendChar(')'); return true; } bool FUniversalObjectLocatorFragment::ImportTextItem(const TCHAR*& Buffer, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText, FArchive* InSerializingArchive) { using namespace UE::UniversalObjectLocator; if (Buffer && *Buffer == '(') { const TCHAR* BufferEnd = FCString::Strchr(Buffer, ')'); if (Buffer != BufferEnd && (BufferEnd - Buffer) < std::numeric_limits::max()) { FStringView View(Buffer+1, int32(BufferEnd-Buffer)-1); if (TryParseString(View, FParseStringParams())) { return true; } } } return false; } bool FUniversalObjectLocatorFragment::SerializeFromMismatchedTag(const FPropertyTag& Tag, FStructuredArchive::FSlot Slot) { return false; } void FUniversalObjectLocatorFragment::GetPreloadDependencies(TArray& OutDeps) { } #undef LOCTEXT_NAMESPACE