Files
UnrealEngine/Engine/Source/Runtime/UniversalObjectLocator/Public/UniversalObjectLocatorFragment.h
2025-05-18 13:04:45 +08:00

477 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreTypes.h"
#include "UObject/Class.h"
#include "UniversalObjectLocatorFwd.h"
#include "UniversalObjectLocatorFragmentType.h"
#include "UniversalObjectLocatorFragmentTypeHandle.h"
#include "UniversalObjectLocatorFragmentDebugging.h"
#include "Templates/Function.h"
#include "UniversalObjectLocatorFragment.generated.h"
/**
* Universal Object Locator (UOL) Fragments provide an extensible mechanism for referencing permanent, transient
* or dynamically created objects relative to an external context. UOLs comprise zero or more nested fragments.
*
* Creation and resolution of a fragment requires a context to be provided;
* normally this will be the object on which the UOL exists as a property.
*
* The way in which the object is referenced is defined by globally registered 'FragmentTypes'
* (See IUniversalObjectLocatorModule::RegisterFragmentType). Each FragmentType can be thought of as somewhat
* equivalent to a www URI fragment type, though the 'path' is not necessarily just a string, but includes
* support for the full set of Engine Property types.
*
* The type is implemented as a type-erased payload block, a fragment type handle and some internal flags.
* Payloads will be allocated using the inline memory if alignment and size constraints allow, but
* will fall back to a heap allocation if necessary. Allocation should be avoided by keeping payload
* types small.
*
* Aligned to 8 bytes, 32 (runtime) or 64 (editor) bytes big.
*/
USTRUCT(BlueprintType)
struct alignas(8) FUniversalObjectLocatorFragment
{
GENERATED_BODY()
using FParseStringResult = UE::UniversalObjectLocator::FParseStringResult;
using FParseStringParams = UE::UniversalObjectLocator::FParseStringParams;
static constexpr FAsciiSet ValidFragmentTypeCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
// Valid fragment delimiters are those that are allowable by RFC3986 for the query part (unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?") excluding '&' which we use to separate fragments within a path
static constexpr FAsciiSet ValidFragmentDelimiters = "%!$'()*+,;=/?:@.~";
static constexpr FAsciiSet ValidFragmentPayloadCharacters = ValidFragmentTypeCharacters | ValidFragmentDelimiters;
/** Make our inline data buffer larger in-editor to support editor-only data without allocation */
#if WITH_EDITORONLY_DATA
static constexpr SIZE_T SizeInMemory = 64;
#else
static constexpr SIZE_T SizeInMemory = 32;
#endif
/**
* Construct a new fragment with a specific fragment type and data.
* Used when a specific type of relative fragment is required.
*
* @param InHandle A typed handle to the fragment type to use for construction. Retrieved from IUniversalObjectLocatorModule::RegisterFragmentType.
* @param InArgs (Optional) Payload construction arguments to be passed to T on construction. Omission implies default construction.
*/
template<typename T, typename ...ArgTypes>
FUniversalObjectLocatorFragment(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InHandle, ArgTypes&& ...InArgs);
/**
* Construct a new fragment with a specific fragment type and default-constructed payload.
* Used when a specific type of relative fragment is required.
*
* @param InFragmentType The fragment type to use with this fragment
*/
UNIVERSALOBJECTLOCATOR_API FUniversalObjectLocatorFragment(const UE::UniversalObjectLocator::FFragmentType& InFragmentType);
/**
* Construct this fragment by binding it to an object within a given context.
* @note: This constructor can 'fail' and result in an Empty fragment if no suitable fragment type could be found for the object
*/
UNIVERSALOBJECTLOCATOR_API FUniversalObjectLocatorFragment(const UObject* InObject, UObject* Context);
/** Default constructor: initializes to an empty fragment with no fragment type */
UNIVERSALOBJECTLOCATOR_API FUniversalObjectLocatorFragment();
/** Destructor - destructs the payload, and frees any heap allocation as necessary */
UNIVERSALOBJECTLOCATOR_API ~FUniversalObjectLocatorFragment();
/** Copy construction/assignment */
UNIVERSALOBJECTLOCATOR_API FUniversalObjectLocatorFragment(const FUniversalObjectLocatorFragment& RHS);
UNIVERSALOBJECTLOCATOR_API FUniversalObjectLocatorFragment& operator=(const FUniversalObjectLocatorFragment& RHS);
/** Move construction/assignment */
UNIVERSALOBJECTLOCATOR_API FUniversalObjectLocatorFragment(FUniversalObjectLocatorFragment&& RHS);
UNIVERSALOBJECTLOCATOR_API FUniversalObjectLocatorFragment& operator=(FUniversalObjectLocatorFragment&& RHS);
/** Equality comparison. Compares the fragment type and payload data. */
UNIVERSALOBJECTLOCATOR_API friend bool operator==(const FUniversalObjectLocatorFragment& A, const FUniversalObjectLocatorFragment& B);
/** Inequality comparison. Compares the fragment type and payload data. */
UNIVERSALOBJECTLOCATOR_API friend bool operator!=(const FUniversalObjectLocatorFragment& A, const FUniversalObjectLocatorFragment& B);
/** Type hashable */
UNIVERSALOBJECTLOCATOR_API friend uint32 GetTypeHash(const FUniversalObjectLocatorFragment& Fragment);
public:
/**
* Attempt to resolve this fragment by invoking the payload's 'Resolve' function
*
* @param Params Resolution parameters, defining the context to resolve within, and the type of resolution to perform
* @return A result structure defining the resolved object pointer, and associated flags
*/
UNIVERSALOBJECTLOCATOR_API UE::UniversalObjectLocator::FResolveResult Resolve(const UE::UniversalObjectLocator::FResolveParams& Params) const;
/**
* Check whether this reference is empty.
* @note: An empty fragment can never resolve to an object, but is distinct from, and not equal to, a populated fragment that does points to a non-existent or irretrievable object.
*/
bool IsEmpty() const
{
return !FragmentType.IsValid();
}
public:
/**
* Reset this fragment back to its default-constructed, empty state
*/
UNIVERSALOBJECTLOCATOR_API void Reset();
/**
* Reset this fragment to point to a new object from the specified context
* @note: If no suitable fragment type could be found for the object and context, results in an Empty fragment
*/
UNIVERSALOBJECTLOCATOR_API void Reset(const UObject* InObject, UObject* Context);
/**
* Reset this fragment to point to a new object from the specified context using a filtered set of fragment types
* @note: If no suitable fragment type could be found for the object and context, results in an Empty fragment
*/
UNIVERSALOBJECTLOCATOR_API void Reset(const UObject* InObject, UObject* Context, TFunctionRef<bool(UE::UniversalObjectLocator::FFragmentTypeHandle)> CanUseFragmentType);
public:
/**
* Convert this fragment to a string of the form fragment-id[=fragment-payload]
*
* @param OutString String builder to populate
*/
UNIVERSALOBJECTLOCATOR_API void ToString(FStringBuilderBase& OutString) const;
/**
* Attempt to initialize this fragment from a string of the form fragment-type-id[=payload]
* The state of this instance will not be changed if this function returns false.
*
* @param InString The string to parse.
* @param InParams Additional string parameters
* @return Parse result, specifying success or failure, and number of characters that were parsed
*/
UNIVERSALOBJECTLOCATOR_API FParseStringResult TryParseString(FStringView InString, const FParseStringParams& InParams);
/**
* Attempt to default initialize this fragment using a string that defines the type
* The state of this instance will not be changed if this function returns false.
*
* @param InString The string to parse.
* @param InParams Additional string parameters
* @return Parse result, specifying success or failure, and number of characters that were parsed
*/
UNIVERSALOBJECTLOCATOR_API FParseStringResult TryParseFragmentType(FStringView InString, const FParseStringParams& InParams);
/**
* Attempt to deserialize this fragment's payload from a string, based on its currently assigned type
* The state of this instance will not be changed if this function returns false.
*
* @param InString The string to parse.
* @param InParams Additional string parameters
* @return Parse result, specifying success or failure, and number of characters that were parsed
*/
UNIVERSALOBJECTLOCATOR_API FParseStringResult TryParseFragmentPayload(FStringView InString, const FParseStringParams& InParams);
public:
/**
* Try and retrieve this fragment's payload as a specific type using its fragment type handle
*
* @param InType The handle of the fragment type to this payload to. This is returned from IUniversalObjectLocatorModule::RegisterFragmentType on registration.
* @return A mutable pointer to the payload, or nullptr if this fragment is empty or of a different type.
*/
template<typename T>
T* GetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InType);
/**
* Try and retrieve this fragment's payload as a specific type using its fragment type handle
*
* @param InType The handle of the fragment type to this payload to. This is returned from IUniversalObjectLocatorModule::RegisterFragmentType on registration.
* @return A mutable pointer to the payload, or nullptr if this fragment is empty or of a different type.
*/
template<typename T>
const T* GetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InType) const;
/**
* Try and retrieve this fragment's payload as a specific type using its fragment type handle
*
* @param InType The handle of the fragment type to this payload to. This is returned from IUniversalObjectLocatorModule::RegisterFragmentType on registration.
* @param OutData Pointer to retrieve the resulting payload ptr
* @return true on success, false if this fragment is empty or of a different type.
*/
template<typename T>
bool TryGetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InType, T*& OutData);
/**
* Try and retrieve this fragment's payload as a specific type using its fragment type handle
*
* @param InType The handle of the fragment type to this payload to. This is returned from IUniversalObjectLocatorModule::RegisterFragmentType on registration.
* @param OutData Pointer to retrieve the resulting payload ptr
* @return true on success, false if this fragment is empty or of a different type.
*/
template<typename T>
bool TryGetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InType, const T*& OutData) const;
/**
* Retrieve this fragment's payload data
*
* @return The payload data or nullptr if this fragment is empty
*/
UNIVERSALOBJECTLOCATOR_API void* GetPayload();
/**
* Retrieve this fragment's payload data
*
* @return The payload data or nullptr if this fragment is empty
*/
UNIVERSALOBJECTLOCATOR_API const void* GetPayload() const;
/**
* Retrieve this fragment's fragment type
*
* @return The fragment type or nullptr if this fragment is empty
*/
UNIVERSALOBJECTLOCATOR_API const UE::UniversalObjectLocator::FFragmentType* GetFragmentType() const;
/**
* Retrieve this fragment's fragment struct type
*
* @return The fragment struct type or nullptr if this fragment is empty
*/
UNIVERSALOBJECTLOCATOR_API UScriptStruct* GetFragmentStruct() const;
/**
* Retrieve this fragment's fragment type handle
*
* @return The fragment type handle (possibly invalid if this fragment is empty)
*/
UNIVERSALOBJECTLOCATOR_API UE::UniversalObjectLocator::FFragmentTypeHandle GetFragmentTypeHandle() const;
public:
/*~ Begin TStructOpsTypeTraits implementation */
UNIVERSALOBJECTLOCATOR_API bool Serialize(FArchive& Ar);
UNIVERSALOBJECTLOCATOR_API void AddStructReferencedObjects(FReferenceCollector& Collector);
UNIVERSALOBJECTLOCATOR_API bool ExportTextItem(FString& ValueStr, const FUniversalObjectLocatorFragment& DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const;
UNIVERSALOBJECTLOCATOR_API bool ImportTextItem(const TCHAR*& Buffer, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText, FArchive* InSerializingArchive = nullptr);
UNIVERSALOBJECTLOCATOR_API bool SerializeFromMismatchedTag(const FPropertyTag& Tag, FStructuredArchive::FSlot Slot);
UNIVERSALOBJECTLOCATOR_API void GetPreloadDependencies(TArray<UObject*>& OutDeps);
/*~ End TStructOpsTypeTraits implementation */
protected:
/** Runtime type-checking against specific struct types. Compiled out if checks are not enabled. */
#if DO_CHECK
/** Runtime type-checking against a specific struct type. Compiled out if checks are not enabled. */
UNIVERSALOBJECTLOCATOR_API void CheckPayloadType(UScriptStruct* TypeToCompare) const;
#else
FORCEINLINE static constexpr void CheckPayloadType(void* TypeToCompare)
{
}
#endif
protected:
/**
* Describes a UOL fragment payload after it has been allocated
* Where UE_UNIVERSALOBJECTLOCATOR_DEBUG is enabled, the allocation will be laid out as such:
* 0.. 8.. 8+sizeof(T)
* [TFragmentPayload<T> | T Payload ]
*
* When UE_UNIVERSALOBJECTLOCATOR_DEBUG is disabled, the allocation is simply:
* 0.. sizeof(T)
* | T Payload ]
*/
struct FAllocatedPayload
{
#if UE_UNIVERSALOBJECTLOCATOR_DEBUG
/** Pointer to artificially inserted vtable ptr that identifies the fragment data polymorphically */
void* DebugVFTablePtr;
#endif
/** Pointer to the actual typed fragment data */
void* Payload;
};
/**
* Allocate (but do not initialize) the fragment payload using the specified size and alignment,
* accounting for enough space to fit a debug vftable ptr whern UE_UNIVERSALOBJECTLOCATOR_DEBUG is enabled.
*
* @return Structure identifying the payload
*/
UNIVERSALOBJECTLOCATOR_API FAllocatedPayload AllocatePayload(size_t Size, size_t Alignment);
/**
* Default-initialize the fragment payload using the specified type
*/
void DefaultConstructPayload(const UE::UniversalObjectLocator::FFragmentType& InFragmentType);
/**
* Destroy the payload (if valid) by calling its destructor and freeing the memory (if necessary)
*/
void DestroyPayload();
private:
uint32 GetDebugHeaderOffset() const
{
// Special case for DebugHeaderSizeLog2==0 which signifies no offset rather than 2^0 = 1 byte.
// We do this using a branchless bitmask that always unsets the first bit (which can never be set, because we always return a power of 2 > 1)
return (1ul << DebugHeaderSizeLog2) & (~1ul);
}
#if UE_UNIVERSALOBJECTLOCATOR_DEBUG
/*~ Utility symbol name to guarantee that FFragmentType can be resolved within the context of a FUniversalObjectLocatorFragment within natvis expressions */
struct FDebuggableFragmentType
{
using Type = UE::UniversalObjectLocator::FFragmentType;
};
struct FDebuggableFragment
{
using Type = UE::UniversalObjectLocator::IFragmentPayload;
};
#endif
/*
* Payload data - implicitly aligned to a 8 byte boundary since it's the first member.
* Given payload type T, this is either a type-erased T() value (where bIsInline==1),
* or a T* to a heap allocated T (where bIsInline==0)
* SizeInMemory is specifically defined by the desired overall size of FUniversalObjectLocatorFragment::SizeInMemory, minus space for other members
*/
uint8 Data[SizeInMemory-2];
/** 1 Byte - the fragment type portion of the universal fragment */
UE::UniversalObjectLocator::FFragmentTypeHandle FragmentType;
/*~ 1 Byte of flags */
/** True when FragmentType has been assigned to a valid handle, and Data has been initialized with FragmentType::PayloadType */
uint8 bIsInitialized : 1;
/** True if Data is an inline allocation of FragmentType::PayloadType, false means Data is a (void*) to the heap allocated data. */
uint8 bIsInline : 1;
/** Offset from the allocated memory to the fragment payload stored as a power of 2. Only non-zero when UE_UNIVERSALOBJECTLOCATOR_DEBUG is enabled. */
uint8 DebugHeaderSizeLog2 : 6;
};
template<>
struct TStructOpsTypeTraits<FUniversalObjectLocatorFragment> : public TStructOpsTypeTraitsBase2<FUniversalObjectLocatorFragment>
{
enum
{
WithSerializer = true,
WithCopy = true,
WithIdenticalViaEquality = true,
WithExportTextItem = true,
WithImportTextItem = true,
WithAddStructReferencedObjects = true,
};
};
template<typename T, typename ...ArgTypes>
FUniversalObjectLocatorFragment::FUniversalObjectLocatorFragment(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InHandle, ArgTypes&& ...InArgs)
: FragmentType(InHandle)
, bIsInitialized(0)
, DebugHeaderSizeLog2(0)
{
using namespace UE::UniversalObjectLocator;
checkf(InHandle, TEXT("Attempting to construct a new fragment from an invalid fragment type handle - was it registered?"));
FAllocatedPayload Allocation = AllocatePayload(sizeof(T), alignof(T));
#if UE_UNIVERSALOBJECTLOCATOR_DEBUG
// Initialize the fragment vftable if necessary. We can do this without needing the fragment type since we know the type
new (Allocation.DebugVFTablePtr) TFragmentPayload<T>;
#endif
// Placement new the payload
new (Allocation.Payload) T{ Forward<ArgTypes>(InArgs)... };
}
template<typename T>
T* FUniversalObjectLocatorFragment::GetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InType)
{
if (FragmentType.IsValid() && ensureMsgf(FragmentType == InType, TEXT("Type mismatch when accessing payload data!")))
{
return static_cast<T*>(GetPayload());
}
return nullptr;
}
template<typename T>
const T* FUniversalObjectLocatorFragment::GetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InType) const
{
return const_cast<FUniversalObjectLocatorFragment*>(this)->GetPayloadAs(InType);
}
template<typename T>
bool FUniversalObjectLocatorFragment::TryGetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InType, T*& OutData)
{
if (FragmentType.IsValid() && FragmentType == InType)
{
OutData = static_cast<T*>(GetPayload());
return true;
}
return false;
}
template<typename T>
bool FUniversalObjectLocatorFragment::TryGetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle<T> InType, const T*& OutData) const
{
if (FragmentType.IsValid() && FragmentType == InType)
{
OutData = static_cast<const T*>(GetPayload());
return true;
}
return false;
}
/** Empty struct type used for deserializing unknown fragment type payloads */
USTRUCT()
struct FUniversalObjectLocatorEmptyPayload
{
GENERATED_BODY()
};
template<typename PayloadType>
struct TUniversalObjectLocatorFragment : FUniversalObjectLocatorFragment
{
TUniversalObjectLocatorFragment()
: FUniversalObjectLocatorFragment(PayloadType::FragmentType)
{
}
TUniversalObjectLocatorFragment(UE::UniversalObjectLocator::TFragmentTypeHandle<PayloadType> InHandle)
: FUniversalObjectLocatorFragment(InHandle)
{
}
template<typename ...ArgTypes>
TUniversalObjectLocatorFragment(ArgTypes&& ...InArgs)
: FUniversalObjectLocatorFragment(PayloadType::FragmentType, Forward<ArgTypes>(InArgs)...)
{
}
template<typename ...ArgTypes>
TUniversalObjectLocatorFragment(UE::UniversalObjectLocator::TFragmentTypeHandle<PayloadType> InHandle, ArgTypes&& ...InArgs)
: FUniversalObjectLocatorFragment(InHandle, Forward<ArgTypes>(InArgs)...)
{
}
PayloadType* GetPayload()
{
CheckPayloadType(PayloadType::StaticStruct());
return static_cast<PayloadType*>(FUniversalObjectLocatorFragment::GetPayload());
}
const PayloadType* GetPayload() const
{
CheckPayloadType(PayloadType::StaticStruct());
return static_cast<const PayloadType*>(FUniversalObjectLocatorFragment::GetPayload());
}
};