You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Universal Object Locators (UOLs) are designed to support referencing objects that don't fit neatly into a basic outer->inner path representation. Examples might include transient actors, dynamically created objects, or objects that need to be referenced by an external ID or using external lookup logic. Specifically this might be an object spawned by Sequencer, a transient object on a USD stage, or a gameplay-specific object created by a game system. A UOL comprises zero or more 'fragments': atomic pieces of data and logic that defines how to lookup or load an object based on a context. Fragment types are globally registered as part of module initialization. UOLs are hashable, and support string conversion that conforms to RFC3986 so they can be used as URIs (though that is not a current use-case). In order to support this type of string conversion, the 'path' part of of a UOL defines the fragment types, and the query string is used to encode the payload data for each fragment. This allows us to support a more diverse set of characters as part of payload strings (ie, / : and .) which are otherwise unsupported as part of the path. An example UOL to an anim instance might look like: uobj://actor/subobj/animinst?payload0=/Path/To/Package.LevelName:PathToActor&payload1=ComponentName #rb david.bromberg, ludovic.chabant, Max.Chen [CL 29714989 by andrew rodham in ue5-main branch]
670 lines
18 KiB
C++
670 lines
18 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "UniversalObjectLocator.h"
|
|
#include "DirectPathObjectLocator.h"
|
|
#include "UniversalObjectLocatorInitializeParams.h"
|
|
#include "UniversalObjectLocatorInitializeResult.h"
|
|
#include "UniversalObjectLocatorStringParams.h"
|
|
#include "UniversalObjectLocatorStringUtils.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "UOL"
|
|
|
|
namespace UE::UniversalObjectLocator
|
|
{
|
|
static constexpr FStringView MagicLeadingString = TEXTVIEW("uobj://");
|
|
|
|
extern TArray<FFragmentType> GFragmentTypes;
|
|
|
|
const FFragmentType* FindBestFragmentType(const UObject* Object, const UObject* Context);
|
|
|
|
} // namespace UE::UniversalObjectLocator
|
|
|
|
bool operator==(const FUniversalObjectLocator& A, const FUniversalObjectLocator& B)
|
|
{
|
|
return A.Fragments == B.Fragments;
|
|
}
|
|
|
|
bool operator!=(const FUniversalObjectLocator& A, const FUniversalObjectLocator& B)
|
|
{
|
|
return A.Fragments != B.Fragments;
|
|
}
|
|
|
|
uint32 GetTypeHash(const FUniversalObjectLocator& Locator)
|
|
{
|
|
return GetTypeHash(Locator.Fragments);
|
|
}
|
|
|
|
FUniversalObjectLocator::FUniversalObjectLocator()
|
|
{
|
|
|
|
}
|
|
|
|
FUniversalObjectLocator::FUniversalObjectLocator(UObject* Object, const UObject* Context, const UObject* StopAtContext)
|
|
{
|
|
Reset(Object, Context, StopAtContext);
|
|
}
|
|
|
|
UE::UniversalObjectLocator::FResolveResult FUniversalObjectLocator::Resolve(const FResolveParams& Params) const
|
|
{
|
|
using namespace UE::UniversalObjectLocator;
|
|
|
|
// Check for invalid combinations of flags
|
|
check(!EnumHasAllFlags(Params.Flags, EResolveFlags::Load | EResolveFlags::Unload));
|
|
// Cannot have WillWait without Async
|
|
check(!EnumHasAllFlags(Params.Flags, EResolveFlags::WillWait) || EnumHasAllFlags(Params.Flags, EResolveFlags::Async));
|
|
|
|
if (EnumHasAnyFlags(Params.Flags, EResolveFlags::Async))
|
|
{
|
|
return ResolveAsyncImpl(Params);
|
|
}
|
|
else
|
|
{
|
|
return ResolveSyncImpl(Params);
|
|
}
|
|
}
|
|
|
|
UE::UniversalObjectLocator::FResolveResult FUniversalObjectLocator::ResolveSyncImpl(const FResolveParams& Params) const
|
|
{
|
|
using namespace UE::UniversalObjectLocator;
|
|
|
|
check(!EnumHasAnyFlags(Params.Flags, EResolveFlags::Async));
|
|
|
|
FResolveResult EmptyResult;
|
|
|
|
if (Fragments.Num() == 0)
|
|
{
|
|
return EmptyResult;
|
|
}
|
|
|
|
if (Fragments.Num() == 1)
|
|
{
|
|
return Fragments[0].Resolve(Params);
|
|
}
|
|
|
|
bool bLoadedIndirectly = false;
|
|
|
|
FResolveResult LastResult;
|
|
|
|
const UObject* CurrentContext = Params.Context;
|
|
const int32 Num = Fragments.Num();
|
|
for (int32 Index = 0; Index < Num; ++Index)
|
|
{
|
|
const FUniversalObjectLocatorFragment& Fragment = Fragments[Index];
|
|
|
|
const bool bLastFragment = Index == (Num-1);
|
|
|
|
// Only unload the last one
|
|
FResolveParams RelativeParams(CurrentContext, Params.Flags);
|
|
if (!bLastFragment)
|
|
{
|
|
RelativeParams.Flags &= ~EResolveFlags::Unload;
|
|
}
|
|
|
|
LastResult = Fragment.Resolve(RelativeParams);
|
|
|
|
if (EnumHasAnyFlags(RelativeParams.Flags, EResolveFlags::Unload))
|
|
{
|
|
return LastResult;
|
|
}
|
|
|
|
FResolveResultData ResultData = LastResult.SyncGet();
|
|
if (ResultData.Object == nullptr)
|
|
{
|
|
// If anything fails to resolve, nothing resolves
|
|
return EmptyResult;
|
|
}
|
|
|
|
CurrentContext = ResultData.Object;
|
|
|
|
// If the last one was implicitly loaded or created, the final result should report that
|
|
|
|
if (bLastFragment)
|
|
{
|
|
ResultData.Flags.bWasLoadedIndirectly = bLoadedIndirectly;
|
|
}
|
|
else
|
|
{
|
|
bLoadedIndirectly |= ResultData.Flags.bWasLoaded;
|
|
}
|
|
}
|
|
|
|
return LastResult;
|
|
}
|
|
|
|
UE::UniversalObjectLocator::FResolveResult FUniversalObjectLocator::ResolveAsyncImpl(const FResolveParams& Params) const
|
|
{
|
|
using namespace UE::UniversalObjectLocator;
|
|
|
|
check(EnumHasAnyFlags(Params.Flags, EResolveFlags::Async));
|
|
|
|
if (Fragments.Num() == 0)
|
|
{
|
|
return FResolveResult();
|
|
}
|
|
|
|
struct FState : TSharedFromThis<FState>
|
|
{
|
|
FState(const TArray<FUniversalObjectLocatorFragment>& InFragments, EResolveFlags InInputResolveFlags)
|
|
: FragmentsCopy(InFragments)
|
|
, CurrentIndex(-1)
|
|
, InputResolveFlags(InInputResolveFlags)
|
|
{}
|
|
|
|
void ProcessNext(FResolveResultData LastResult)
|
|
{
|
|
const int32 Index = ++CurrentIndex;
|
|
|
|
const bool bFinished = Index == FragmentsCopy.Num();
|
|
const bool bLastFragment = Index == (FragmentsCopy.Num()-1);
|
|
|
|
UObject* Context = LastResult.Object;
|
|
|
|
// If the previous one was loaded or created, any subsequent operations are loaded indirectly
|
|
if (!bLastFragment)
|
|
{
|
|
bLoadedIndirectly = LastResult.Flags.bWasLoaded;
|
|
}
|
|
|
|
const bool bCannotContinue = Context == nullptr && Index != 0;
|
|
if (bFinished || bCannotContinue)
|
|
{
|
|
if (AsyncResult.IsSet())
|
|
{
|
|
LastResult.Flags.bWasLoadedIndirectly = bLoadedIndirectly;
|
|
AsyncResult->SetValue(LastResult);
|
|
}
|
|
else
|
|
{
|
|
FinalResult = FResolveResult(LastResult);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Try and resolve this one
|
|
FResolveParams RelativeParams(Context, InputResolveFlags);
|
|
|
|
// Only unload the last one
|
|
if (!bLastFragment)
|
|
{
|
|
RelativeParams.Flags &= ~EResolveFlags::Unload;
|
|
}
|
|
|
|
FResolveResult Result = FragmentsCopy[Index].Resolve(RelativeParams);
|
|
|
|
// If we don't need to wait, call the next one immediately
|
|
if (!Result.NeedsWait())
|
|
{
|
|
ProcessNext(Result.SyncGet());
|
|
return;
|
|
}
|
|
|
|
// We need to wait for something to complete...
|
|
// If the currently held FinalResult is not async, we need to make it so
|
|
if (!FinalResult.IsAsync())
|
|
{
|
|
check(!AsyncResult.IsSet());
|
|
AsyncResult.Emplace();
|
|
FinalResult = FResolveResult(AsyncResult->GetFuture());
|
|
}
|
|
|
|
// Set the continuation
|
|
Result.AsyncGet(
|
|
[State = AsShared()](const FResolveResultData& InValue)
|
|
{
|
|
State->ProcessNext(InValue);
|
|
}
|
|
);
|
|
}
|
|
|
|
TArray<FUniversalObjectLocatorFragment> FragmentsCopy;
|
|
|
|
TOptional<
|
|
TPromise<FResolveResultData>
|
|
> AsyncResult;
|
|
|
|
FResolveResult FinalResult;
|
|
|
|
int32 CurrentIndex;
|
|
EResolveFlags InputResolveFlags;
|
|
bool bLoadedIndirectly = false;
|
|
};
|
|
|
|
TSharedPtr<FState> SharedState = MakeShared<FState>(this->Fragments, Params.Flags);
|
|
SharedState->ProcessNext(nullptr);
|
|
return MoveTemp(SharedState->FinalResult);
|
|
}
|
|
|
|
UObject* FUniversalObjectLocator::SyncFind(const UObject* Context) const
|
|
{
|
|
return Resolve(FResolveParams::SyncFind(Context)).SyncGet().Object;
|
|
}
|
|
|
|
UObject* FUniversalObjectLocator::SyncLoad(const UObject* Context) const
|
|
{
|
|
return Resolve(FResolveParams::SyncLoad(Context)).SyncGet().Object;
|
|
}
|
|
|
|
void FUniversalObjectLocator::SyncUnload(const UObject* Context) const
|
|
{
|
|
Resolve(FResolveParams::SyncUnload(Context)).SyncGet();
|
|
}
|
|
|
|
UE::UniversalObjectLocator::FResolveResult FUniversalObjectLocator::AsyncFind(const UObject* Context) const
|
|
{
|
|
return Resolve(FResolveParams::AsyncFind(Context));
|
|
}
|
|
|
|
UE::UniversalObjectLocator::FResolveResult FUniversalObjectLocator::AsyncLoad(const UObject* Context) const
|
|
{
|
|
return Resolve(FResolveParams::AsyncLoad(Context));
|
|
}
|
|
|
|
UE::UniversalObjectLocator::FResolveResult FUniversalObjectLocator::AsyncUnload(const UObject* Context) const
|
|
{
|
|
return Resolve(FResolveParams::AsyncUnload(Context));
|
|
}
|
|
|
|
void FUniversalObjectLocator::Reset()
|
|
{
|
|
Fragments.Empty();
|
|
}
|
|
|
|
void FUniversalObjectLocator::Reset(UObject* InObject, const UObject* Context, const UObject* StopAtContext)
|
|
{
|
|
Fragments.Reset();
|
|
if (!InObject || !AddFragment(InObject, Context, StopAtContext))
|
|
{
|
|
// Failed to create the locator
|
|
Fragments.Empty();
|
|
}
|
|
}
|
|
|
|
const UE::UniversalObjectLocator::FFragmentType* FUniversalObjectLocator::GetLastFragmentType() const
|
|
{
|
|
return Fragments.Num() != 0 ? Fragments.Last().GetFragmentType() : nullptr;
|
|
}
|
|
|
|
UE::UniversalObjectLocator::FFragmentTypeHandle FUniversalObjectLocator::GetLastFragmentTypeHandle() const
|
|
{
|
|
return Fragments.Num() != 0 ? Fragments.Last().GetFragmentTypeHandle() : UE::UniversalObjectLocator::FFragmentTypeHandle();
|
|
}
|
|
|
|
FUniversalObjectLocatorFragment* FUniversalObjectLocator::GetLastFragment()
|
|
{
|
|
return Fragments.Num() != 0 ? &Fragments.Last() : nullptr;
|
|
}
|
|
|
|
const FUniversalObjectLocatorFragment* FUniversalObjectLocator::GetLastFragment() const
|
|
{
|
|
return Fragments.Num() != 0 ? &Fragments.Last() : nullptr;
|
|
}
|
|
|
|
void FUniversalObjectLocator::ToString(FStringBuilderBase& OutString) const
|
|
{
|
|
using namespace UE::UniversalObjectLocator;
|
|
|
|
// Universal Object Locators currently write to strings of the following form:
|
|
// uobj://fragment-type-id=payload-string!...!fragment-type-id-n=payload-string-n
|
|
|
|
TStringBuilder<128> PayloadScratchSpace;
|
|
|
|
OutString += MagicLeadingString;
|
|
|
|
int32 NumPrintedTypes = 0;
|
|
int32 NumPrintedPayloads = 0;
|
|
|
|
// Print the fragment types as the path
|
|
for (int32 Index = 0; Index < Fragments.Num(); ++Index)
|
|
{
|
|
const FUniversalObjectLocatorFragment& Fragment = Fragments[Index];
|
|
if (const FFragmentType* FragmentTypePtr = Fragment.GetFragmentType())
|
|
{
|
|
if (NumPrintedTypes != 0)
|
|
{
|
|
OutString += '/';
|
|
}
|
|
|
|
FragmentTypePtr->FragmentTypeID.AppendString(OutString);
|
|
++NumPrintedTypes;
|
|
}
|
|
}
|
|
|
|
if (NumPrintedTypes == 0)
|
|
{
|
|
OutString += TEXT("none");
|
|
return;
|
|
}
|
|
|
|
OutString += TEXT("?");
|
|
|
|
// Print the payloads as a query string
|
|
for (int32 Index = 0; Index < Fragments.Num(); ++Index)
|
|
{
|
|
const FUniversalObjectLocatorFragment& Fragment = Fragments[Index];
|
|
|
|
const FFragmentType* FragmentTypePtr = Fragment.GetFragmentType();
|
|
const UStruct* FragmentStruct = FragmentTypePtr ? FragmentTypePtr->GetStruct() : nullptr;
|
|
|
|
if (FragmentStruct)
|
|
{
|
|
if (NumPrintedPayloads != 0)
|
|
{
|
|
OutString += '&';
|
|
}
|
|
|
|
PayloadScratchSpace.Reset();
|
|
FragmentTypePtr->ToString(Fragment.GetPayload(), PayloadScratchSpace);
|
|
if (PayloadScratchSpace.Len() != 0)
|
|
{
|
|
OutString.Appendf(TEXT("payload%i="), Index);
|
|
OutString.Append(PayloadScratchSpace.ToView());
|
|
}
|
|
|
|
++NumPrintedPayloads;
|
|
}
|
|
}
|
|
}
|
|
|
|
UE::UniversalObjectLocator::FParseStringResult FUniversalObjectLocator::TryParseString(FStringView InString, const FParseStringParams& InParams)
|
|
{
|
|
using namespace UE::UniversalObjectLocator;
|
|
|
|
FParseStringResult Result;
|
|
|
|
if (!InString.StartsWith(MagicLeadingString, ESearchCase::IgnoreCase))
|
|
{
|
|
return Result.Failure(UE_UOL_PARSE_ERROR(InParams, LOCTEXT("Error_MissingScheme", "String does not start with uobj://")));
|
|
}
|
|
|
|
InString = Result.Progress(InString, MagicLeadingString.Len());
|
|
if (InString.Len() == 0)
|
|
{
|
|
return Result.Failure(UE_UOL_PARSE_ERROR(InParams, LOCTEXT("Error_EmptyPath", "Path is empty.")));
|
|
}
|
|
|
|
if (InString.Compare(TEXTVIEW("none"), ESearchCase::IgnoreCase) == 0)
|
|
{
|
|
Reset();
|
|
return Result.Success();
|
|
}
|
|
|
|
FUniversalObjectLocator Tmp;
|
|
|
|
// First parse the fragment types
|
|
bool bFinishedParsingFragments = false;
|
|
while (InString.Len() != 0 && !bFinishedParsingFragments)
|
|
{
|
|
// Find the next / or ? character
|
|
const int32 NextDelimiter = UE::String::FindFirstOfAnyChar(InString, TEXTVIEW("/?#"));
|
|
|
|
FStringView ThisFragment = InString;
|
|
if (NextDelimiter != INDEX_NONE)
|
|
{
|
|
// If the delimiter is a ? this is the last fragment, but there might be
|
|
// some additional payload string to parse
|
|
bFinishedParsingFragments = (InString[NextDelimiter] == '?');
|
|
|
|
// Trim the fragment string to the delimiter that we found
|
|
// and move the remaining string to after the delimiter
|
|
ThisFragment = InString.Left(NextDelimiter);
|
|
|
|
// Now trim the rest of the string to remove this fragment.
|
|
// If the delimiter is a # then we have nothing more to parse
|
|
if (InString[NextDelimiter] == '#')
|
|
{
|
|
// Indicate that we finished parsing at the #, but reset the string
|
|
// so that we stop parsing anything else
|
|
Result.NumCharsParsed += NextDelimiter;
|
|
InString.Reset();
|
|
}
|
|
else
|
|
{
|
|
// Progress past the delimiter
|
|
InString = Result.Progress(InString, NextDelimiter + 1);
|
|
}
|
|
|
|
// Empty string - just move on to the next one
|
|
if (ThisFragment.Len() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No delimiter found, so the rest of the string is parsed as a fragment type
|
|
Result.NumCharsParsed += InString.Len();
|
|
InString.Reset();
|
|
}
|
|
|
|
FUniversalObjectLocatorFragment& NewFragment = Tmp.Fragments.Emplace_GetRef();
|
|
|
|
FParseStringResult TypeResult = NewFragment.TryParseFragmentType(ThisFragment, InParams);
|
|
Result.NumCharsParsed += TypeResult.NumCharsParsed;
|
|
if (!TypeResult)
|
|
{
|
|
return Result.Failure(
|
|
UE_UOL_PARSE_ERROR(
|
|
InParams,
|
|
FText::Format(
|
|
LOCTEXT("Error_InvalidFragmentType", "Fragment Type specifier is invalid: {0}."),
|
|
FText::FromStringView(ThisFragment)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
// Next parse the fragment payloads
|
|
bool bFinishedParsingPayloads = false;
|
|
while (InString.Len() != 0 && !bFinishedParsingPayloads)
|
|
{
|
|
// Find the next & or # character
|
|
const int32 NextDelimiter = UE::String::FindFirstOfAnyChar(InString, TEXTVIEW("&#"));
|
|
|
|
FStringView ThisPayload = InString;
|
|
if (NextDelimiter != INDEX_NONE)
|
|
{
|
|
// If the delimiter is a # this is the last payload, so we finish parsing
|
|
// after this one
|
|
bFinishedParsingFragments = (InString[NextDelimiter] == '#');
|
|
|
|
// Trim the fragment string to the delimiter that we found
|
|
// and move the remaining string to after the delimiter
|
|
ThisPayload = InString.Left(NextDelimiter);
|
|
|
|
// Progress past the delimiter
|
|
InString = Result.Progress(InString, NextDelimiter + 1);
|
|
|
|
// Empty string - just move on to the next one
|
|
if (ThisPayload.Len() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No delimiter - parse the remaining string as a payload and finish parsing
|
|
Result.NumCharsParsed += InString.Len();
|
|
InString.Reset();
|
|
}
|
|
|
|
static constexpr FStringView PayloadString = TEXTVIEW("payload");
|
|
|
|
// Try and parse a fragment index - any other query string key=value pairs will be skipped
|
|
const int32 EqualsDelimiter = UE::String::FindFirstChar(ThisPayload, '=');
|
|
if (EqualsDelimiter != INDEX_NONE && ThisPayload.StartsWith(PayloadString, ESearchCase::IgnoreCase) && ThisPayload.Len() > 8)
|
|
{
|
|
int32 NumChars = 0;
|
|
uint32 ParsedIndex = 0;
|
|
// Parse an integer from the position after the 'payload' string
|
|
if (!ParseUnsignedInteger(ThisPayload.RightChop(PayloadString.Len()), ParsedIndex, &NumChars) || ParsedIndex > static_cast<uint32>(std::numeric_limits<int32>::max()))
|
|
{
|
|
// Invalid int
|
|
Result.NumCharsParsed += ThisPayload.Len();
|
|
continue;
|
|
}
|
|
else if (ParsedIndex >= static_cast<uint32>(Tmp.Fragments.Num()))
|
|
{
|
|
// Invalid index
|
|
Result.NumCharsParsed += ThisPayload.Len();
|
|
continue;
|
|
}
|
|
|
|
// Handle non-sensical payload strings like payload0123another=
|
|
if (PayloadString.Len() + NumChars != EqualsDelimiter)
|
|
{
|
|
Result.NumCharsParsed += ThisPayload.Len();
|
|
continue;
|
|
}
|
|
|
|
// Skip over the index and =
|
|
ThisPayload = Result.Progress(ThisPayload, PayloadString.Len() + NumChars + 1);
|
|
|
|
if (ThisPayload.Len() == 0)
|
|
{
|
|
// Leave the fragment as default if there is an empty string
|
|
continue;
|
|
}
|
|
|
|
const int32 FragmentIndex = static_cast<int32>(ParsedIndex);
|
|
if (!Tmp.Fragments.IsValidIndex(FragmentIndex))
|
|
{
|
|
// Invalid fragment index for this payload - skip it
|
|
continue;
|
|
}
|
|
|
|
FUniversalObjectLocatorFragment& Fragment = Tmp.Fragments[FragmentIndex];
|
|
|
|
FParseStringResult PayloadResult = Fragment.TryParseFragmentPayload(ThisPayload, InParams);
|
|
Result.NumCharsParsed += PayloadResult.NumCharsParsed;
|
|
if (!PayloadResult)
|
|
{
|
|
const FFragmentType* Type = Fragment.GetFragmentType();
|
|
|
|
return Result.Failure(
|
|
UE_UOL_PARSE_ERROR(InParams,
|
|
FText::Format(
|
|
LOCTEXT("Error_InvalidPayload", "Payload is invalid for fragment type {0}: {1}."),
|
|
FText::FromName(Type ? Type->FragmentTypeID : NAME_None),
|
|
FText::FromStringView(ThisPayload)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
*this = MoveTemp(Tmp);
|
|
return Result.Success();
|
|
}
|
|
|
|
FUniversalObjectLocator FUniversalObjectLocator::FromString(FStringView InString, const FParseStringParams& InParams)
|
|
{
|
|
FUniversalObjectLocator Locator;
|
|
Locator.TryParseString(InString, InParams);
|
|
return Locator;
|
|
}
|
|
|
|
bool FUniversalObjectLocator::AddFragment(const UObject* Object, const UObject* Context, const UObject* StopAtContext)
|
|
{
|
|
using namespace UE::UniversalObjectLocator;
|
|
|
|
const FFragmentType* FragmentType = FindBestFragmentType(Object, Context);
|
|
if (!FragmentType)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Initialize the payload
|
|
FUniversalObjectLocatorFragment NewLocator(*FragmentType);
|
|
FInitializeResult Result = FragmentType->InitializePayload(NewLocator.GetPayload(), FInitializeParams { Object, Context });
|
|
|
|
if (Result.Type == ELocatorType::Undefined)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If the initialization needs to be relative to a different context, add a fragment for NewContext as well
|
|
if (Result.Type == ELocatorType::Relative && Result.RelativeToContext != Context)
|
|
{
|
|
if (ensureMsgf(Result.RelativeToContext, TEXT("Payload initialization reported a relative locator but did not specify what it is relative to.")))
|
|
{
|
|
if (StopAtContext != Result.RelativeToContext)
|
|
{
|
|
AddFragment(Result.RelativeToContext, nullptr, StopAtContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now add ours onto the tail
|
|
Fragments.Emplace(MoveTemp(NewLocator));
|
|
return true;
|
|
}
|
|
|
|
bool FUniversalObjectLocator::SerializeFromMismatchedTag(const FPropertyTag& Tag, FStructuredArchive::FSlot Slot)
|
|
{
|
|
static const FName NAME_SoftObjectPath = "SoftObjectPath";
|
|
|
|
if (Tag.Type == NAME_SoftObjectProperty)
|
|
{
|
|
FSoftObjectPtr OldProperty;
|
|
Slot << OldProperty;
|
|
|
|
Fragments.Reset(1);
|
|
Fragments.Emplace(TUniversalObjectLocatorFragment<FDirectPathObjectLocator>(OldProperty.ToSoftObjectPath()));
|
|
return true;
|
|
}
|
|
else if (Tag.Type == NAME_StructProperty && Tag.StructName == NAME_SoftObjectPath)
|
|
{
|
|
FSoftObjectPath OldPath;
|
|
Slot << OldPath;
|
|
|
|
Fragments.Reset(1);
|
|
Fragments.Emplace(TUniversalObjectLocatorFragment<FDirectPathObjectLocator>(MoveTemp(OldPath)));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FUniversalObjectLocator::ExportTextItem(FString& ValueStr, const FUniversalObjectLocator& DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const
|
|
{
|
|
TStringBuilder<128> String;
|
|
ToString(String);
|
|
|
|
ValueStr.AppendChar('(');
|
|
ValueStr.Append(String.ToString(), String.Len());
|
|
ValueStr.AppendChar(')');
|
|
return true;
|
|
}
|
|
|
|
bool FUniversalObjectLocator::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<int32>::max())
|
|
{
|
|
FStringView View(Buffer + 1, int32(BufferEnd - Buffer) - 1);
|
|
if (TryParseString(View, FParseStringParams()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try and parse this as a soft object path
|
|
FSoftObjectPath Path;
|
|
if (Path.ImportTextItem(Buffer, PortFlags, Parent, ErrorText, InSerializingArchive))
|
|
{
|
|
UObject* Object = Path.ResolveObject();
|
|
Reset(Object);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |