// 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 FUniversalObjectLocatorFragment(UE::UniversalObjectLocator::TFragmentTypeHandle 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 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 T* GetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle 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 const T* GetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle 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 bool TryGetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle 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 bool TryGetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle 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& 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 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 : public TStructOpsTypeTraitsBase2 { enum { WithSerializer = true, WithCopy = true, WithIdenticalViaEquality = true, WithExportTextItem = true, WithImportTextItem = true, WithAddStructReferencedObjects = true, }; }; template FUniversalObjectLocatorFragment::FUniversalObjectLocatorFragment(UE::UniversalObjectLocator::TFragmentTypeHandle 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; #endif // Placement new the payload new (Allocation.Payload) T{ Forward(InArgs)... }; } template T* FUniversalObjectLocatorFragment::GetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle InType) { if (FragmentType.IsValid() && ensureMsgf(FragmentType == InType, TEXT("Type mismatch when accessing payload data!"))) { return static_cast(GetPayload()); } return nullptr; } template const T* FUniversalObjectLocatorFragment::GetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle InType) const { return const_cast(this)->GetPayloadAs(InType); } template bool FUniversalObjectLocatorFragment::TryGetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle InType, T*& OutData) { if (FragmentType.IsValid() && FragmentType == InType) { OutData = static_cast(GetPayload()); return true; } return false; } template bool FUniversalObjectLocatorFragment::TryGetPayloadAs(UE::UniversalObjectLocator::TFragmentTypeHandle InType, const T*& OutData) const { if (FragmentType.IsValid() && FragmentType == InType) { OutData = static_cast(GetPayload()); return true; } return false; } /** Empty struct type used for deserializing unknown fragment type payloads */ USTRUCT() struct FUniversalObjectLocatorEmptyPayload { GENERATED_BODY() }; template struct TUniversalObjectLocatorFragment : FUniversalObjectLocatorFragment { TUniversalObjectLocatorFragment() : FUniversalObjectLocatorFragment(PayloadType::FragmentType) { } TUniversalObjectLocatorFragment(UE::UniversalObjectLocator::TFragmentTypeHandle InHandle) : FUniversalObjectLocatorFragment(InHandle) { } template TUniversalObjectLocatorFragment(ArgTypes&& ...InArgs) : FUniversalObjectLocatorFragment(PayloadType::FragmentType, Forward(InArgs)...) { } template TUniversalObjectLocatorFragment(UE::UniversalObjectLocator::TFragmentTypeHandle InHandle, ArgTypes&& ...InArgs) : FUniversalObjectLocatorFragment(InHandle, Forward(InArgs)...) { } PayloadType* GetPayload() { CheckPayloadType(PayloadType::StaticStruct()); return static_cast(FUniversalObjectLocatorFragment::GetPayload()); } const PayloadType* GetPayload() const { CheckPayloadType(PayloadType::StaticStruct()); return static_cast(FUniversalObjectLocatorFragment::GetPayload()); } };