2021-04-20 02:43:47 -04:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
|
|
#include "InstancedStructDetails.h"
|
|
|
|
|
#include "DetailWidgetRow.h"
|
|
|
|
|
#include "DetailLayoutBuilder.h"
|
2024-03-26 20:27:44 -04:00
|
|
|
#include "Editor.h"
|
|
|
|
|
#include "Editor/EditorEngine.h"
|
2021-04-20 02:43:47 -04:00
|
|
|
#include "IDetailChildrenBuilder.h"
|
2024-05-24 16:10:25 -04:00
|
|
|
#include "IDetailGroup.h"
|
2023-05-26 12:46:32 -04:00
|
|
|
#include "IPropertyUtilities.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "UObject/Package.h"
|
2021-04-20 02:43:47 -04:00
|
|
|
#include "Widgets/Images/SImage.h"
|
|
|
|
|
#include "Modules/ModuleManager.h"
|
2024-06-19 08:00:57 -04:00
|
|
|
#include "StructUtils/UserDefinedStruct.h"
|
2024-06-19 15:22:25 -04:00
|
|
|
#include "StructUtils/InstancedStruct.h"
|
2023-04-27 10:06:33 -04:00
|
|
|
#include "IStructureDataProvider.h"
|
|
|
|
|
#include "Misc/ConfigCacheIni.h"
|
2023-05-26 12:46:32 -04:00
|
|
|
#include "StructUtilsDelegates.h"
|
2024-06-03 16:11:51 -04:00
|
|
|
#include "SInstancedStructPicker.h"
|
2021-04-20 02:43:47 -04:00
|
|
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "StructUtilsEditor"
|
|
|
|
|
|
2023-04-27 10:06:33 -04:00
|
|
|
////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
class FInstancedStructProvider : public IStructureDataProvider
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
FInstancedStructProvider() = default;
|
|
|
|
|
|
|
|
|
|
explicit FInstancedStructProvider(const TSharedPtr<IPropertyHandle>& InStructProperty)
|
|
|
|
|
: StructProperty(InStructProperty)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual ~FInstancedStructProvider() override {}
|
|
|
|
|
|
2023-05-26 12:46:32 -04:00
|
|
|
void Reset()
|
|
|
|
|
{
|
|
|
|
|
StructProperty = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 10:06:33 -04:00
|
|
|
virtual bool IsValid() const override
|
|
|
|
|
{
|
[Backout] - CL33242681
[FYI] marshall.beachy
Original CL Desc
-----------------------------------------------------------------
Performance improvement for nested FInstancedStruct in details panel
* Pathological performance issues occurred when viewing/editing USTRUCT objects that nested FInstancedStruct (either directly or in containers). The PropertyNode system's implementation when handling FInstancedStructs would recurse up the property tree to discover the address of the owning objects and then recurse again up with the starting address. The root cause of the performance issue is that the recursion would branch on the way up in the following functions: GetInstancesNum, GetMemoryOfInstance, GetOwnerPackages, HasValidStructData (by way of IsValid). On the way up again, branches would occur in GetValueBaseAddress, via duplicate calls in FItemPropertyNode::GetValueBaseAddress to ParentNode->GetValueAddress.
* The problem has been addressed by having the StructPropertyNode handle StructProviders that perform indirection differently than other providers. When indirection is present, walk up to the parent node of the struct which will be an ItemPropertyNode - the address of this is retrieved and passed back down to the StructProvider. In the current codebase, the FInstancedStructProvider is the only one that does this type of indirection - it can make the assumption that the passed in address is an FInstancedStruct and thus reinterpret it to get the StructMemory and UStruct* pointers.
* Added an InternalGetReadAddressUncached which public GetReadAddress** functions will call. This new function will not resolve the Struct in the case of a Struct property indirection unlike GetReadAddressUncached. It is intended as an internal function only to be called by the public APIs.
* Added a safeguard canary value to FInstancedStruct to debug/catch cases where the wrong raw void* is reinterpreted as an FInstancedStruct. FInstancedStruct::CastFromVoid helper should be used over a naked reinterpret_cast
* Note that it isn't guarantees all paths where slow recursive behaviour occurs has been addressed in this CL. Specifically, the InstancedStructDetails implementations to get the dispaly values still go through the data enumeration though it should be better now that GetInstancesNum and GetMemoryOfInstance are not recursively calling the Enumerate pathways. See GetDisplayValueString, GetTooltipText, GetDisplayValueIcon, GenerateStructPicker - this is the primary expense in Slate that is probably causing additional performance loss.
#rb mikko.mononen
#jira UE-207555
[CL 33319481 by logan buchy in ue5-main branch]
2024-04-30 01:05:58 -04:00
|
|
|
bool bHasValidData = false;
|
|
|
|
|
EnumerateInstances([&bHasValidData](const UScriptStruct* ScriptStruct, uint8* Memory, UPackage* Package)
|
|
|
|
|
{
|
|
|
|
|
if (ScriptStruct && Memory)
|
|
|
|
|
{
|
|
|
|
|
bHasValidData = true;
|
|
|
|
|
return false; // Stop
|
|
|
|
|
}
|
|
|
|
|
return true; // Continue
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return bHasValidData;
|
2023-04-27 10:06:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual const UStruct* GetBaseStructure() const override
|
|
|
|
|
{
|
2023-06-08 06:10:17 -04:00
|
|
|
// Taken from UClass::FindCommonBase
|
|
|
|
|
auto FindCommonBaseStruct = [](const UScriptStruct* StructA, const UScriptStruct* StructB)
|
2023-04-27 10:06:33 -04:00
|
|
|
{
|
2023-06-08 06:10:17 -04:00
|
|
|
const UScriptStruct* CommonBaseStruct = StructA;
|
|
|
|
|
while (CommonBaseStruct && StructB && !StructB->IsChildOf(CommonBaseStruct))
|
2023-04-27 10:06:33 -04:00
|
|
|
{
|
2023-06-08 06:10:17 -04:00
|
|
|
CommonBaseStruct = Cast<UScriptStruct>(CommonBaseStruct->GetSuperStruct());
|
|
|
|
|
}
|
|
|
|
|
return CommonBaseStruct;
|
|
|
|
|
};
|
[Backout] - CL33242681
[FYI] marshall.beachy
Original CL Desc
-----------------------------------------------------------------
Performance improvement for nested FInstancedStruct in details panel
* Pathological performance issues occurred when viewing/editing USTRUCT objects that nested FInstancedStruct (either directly or in containers). The PropertyNode system's implementation when handling FInstancedStructs would recurse up the property tree to discover the address of the owning objects and then recurse again up with the starting address. The root cause of the performance issue is that the recursion would branch on the way up in the following functions: GetInstancesNum, GetMemoryOfInstance, GetOwnerPackages, HasValidStructData (by way of IsValid). On the way up again, branches would occur in GetValueBaseAddress, via duplicate calls in FItemPropertyNode::GetValueBaseAddress to ParentNode->GetValueAddress.
* The problem has been addressed by having the StructPropertyNode handle StructProviders that perform indirection differently than other providers. When indirection is present, walk up to the parent node of the struct which will be an ItemPropertyNode - the address of this is retrieved and passed back down to the StructProvider. In the current codebase, the FInstancedStructProvider is the only one that does this type of indirection - it can make the assumption that the passed in address is an FInstancedStruct and thus reinterpret it to get the StructMemory and UStruct* pointers.
* Added an InternalGetReadAddressUncached which public GetReadAddress** functions will call. This new function will not resolve the Struct in the case of a Struct property indirection unlike GetReadAddressUncached. It is intended as an internal function only to be called by the public APIs.
* Added a safeguard canary value to FInstancedStruct to debug/catch cases where the wrong raw void* is reinterpreted as an FInstancedStruct. FInstancedStruct::CastFromVoid helper should be used over a naked reinterpret_cast
* Note that it isn't guarantees all paths where slow recursive behaviour occurs has been addressed in this CL. Specifically, the InstancedStructDetails implementations to get the dispaly values still go through the data enumeration though it should be better now that GetInstancesNum and GetMemoryOfInstance are not recursively calling the Enumerate pathways. See GetDisplayValueString, GetTooltipText, GetDisplayValueIcon, GenerateStructPicker - this is the primary expense in Slate that is probably causing additional performance loss.
#rb mikko.mononen
#jira UE-207555
[CL 33319481 by logan buchy in ue5-main branch]
2024-04-30 01:05:58 -04:00
|
|
|
|
2023-06-08 06:10:17 -04:00
|
|
|
const UScriptStruct* CommonStruct = nullptr;
|
|
|
|
|
EnumerateInstances([&CommonStruct, &FindCommonBaseStruct](const UScriptStruct* ScriptStruct, uint8* Memory, UPackage* Package)
|
|
|
|
|
{
|
|
|
|
|
if (ScriptStruct)
|
|
|
|
|
{
|
|
|
|
|
CommonStruct = FindCommonBaseStruct(ScriptStruct, CommonStruct);
|
2023-04-27 10:06:33 -04:00
|
|
|
}
|
|
|
|
|
return true; // Continue
|
|
|
|
|
});
|
[Backout] - CL33242681
[FYI] marshall.beachy
Original CL Desc
-----------------------------------------------------------------
Performance improvement for nested FInstancedStruct in details panel
* Pathological performance issues occurred when viewing/editing USTRUCT objects that nested FInstancedStruct (either directly or in containers). The PropertyNode system's implementation when handling FInstancedStructs would recurse up the property tree to discover the address of the owning objects and then recurse again up with the starting address. The root cause of the performance issue is that the recursion would branch on the way up in the following functions: GetInstancesNum, GetMemoryOfInstance, GetOwnerPackages, HasValidStructData (by way of IsValid). On the way up again, branches would occur in GetValueBaseAddress, via duplicate calls in FItemPropertyNode::GetValueBaseAddress to ParentNode->GetValueAddress.
* The problem has been addressed by having the StructPropertyNode handle StructProviders that perform indirection differently than other providers. When indirection is present, walk up to the parent node of the struct which will be an ItemPropertyNode - the address of this is retrieved and passed back down to the StructProvider. In the current codebase, the FInstancedStructProvider is the only one that does this type of indirection - it can make the assumption that the passed in address is an FInstancedStruct and thus reinterpret it to get the StructMemory and UStruct* pointers.
* Added an InternalGetReadAddressUncached which public GetReadAddress** functions will call. This new function will not resolve the Struct in the case of a Struct property indirection unlike GetReadAddressUncached. It is intended as an internal function only to be called by the public APIs.
* Added a safeguard canary value to FInstancedStruct to debug/catch cases where the wrong raw void* is reinterpreted as an FInstancedStruct. FInstancedStruct::CastFromVoid helper should be used over a naked reinterpret_cast
* Note that it isn't guarantees all paths where slow recursive behaviour occurs has been addressed in this CL. Specifically, the InstancedStructDetails implementations to get the dispaly values still go through the data enumeration though it should be better now that GetInstancesNum and GetMemoryOfInstance are not recursively calling the Enumerate pathways. See GetDisplayValueString, GetTooltipText, GetDisplayValueIcon, GenerateStructPicker - this is the primary expense in Slate that is probably causing additional performance loss.
#rb mikko.mononen
#jira UE-207555
[CL 33319481 by logan buchy in ue5-main branch]
2024-04-30 01:05:58 -04:00
|
|
|
|
2023-04-27 10:06:33 -04:00
|
|
|
return CommonStruct;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-23 07:53:34 -04:00
|
|
|
virtual void GetInstances(TArray<TSharedPtr<FStructOnScope>>& OutInstances, const UStruct* ExpectedBaseStructure) const override
|
2023-04-27 10:06:33 -04:00
|
|
|
{
|
[Backout] - CL33242681
[FYI] marshall.beachy
Original CL Desc
-----------------------------------------------------------------
Performance improvement for nested FInstancedStruct in details panel
* Pathological performance issues occurred when viewing/editing USTRUCT objects that nested FInstancedStruct (either directly or in containers). The PropertyNode system's implementation when handling FInstancedStructs would recurse up the property tree to discover the address of the owning objects and then recurse again up with the starting address. The root cause of the performance issue is that the recursion would branch on the way up in the following functions: GetInstancesNum, GetMemoryOfInstance, GetOwnerPackages, HasValidStructData (by way of IsValid). On the way up again, branches would occur in GetValueBaseAddress, via duplicate calls in FItemPropertyNode::GetValueBaseAddress to ParentNode->GetValueAddress.
* The problem has been addressed by having the StructPropertyNode handle StructProviders that perform indirection differently than other providers. When indirection is present, walk up to the parent node of the struct which will be an ItemPropertyNode - the address of this is retrieved and passed back down to the StructProvider. In the current codebase, the FInstancedStructProvider is the only one that does this type of indirection - it can make the assumption that the passed in address is an FInstancedStruct and thus reinterpret it to get the StructMemory and UStruct* pointers.
* Added an InternalGetReadAddressUncached which public GetReadAddress** functions will call. This new function will not resolve the Struct in the case of a Struct property indirection unlike GetReadAddressUncached. It is intended as an internal function only to be called by the public APIs.
* Added a safeguard canary value to FInstancedStruct to debug/catch cases where the wrong raw void* is reinterpreted as an FInstancedStruct. FInstancedStruct::CastFromVoid helper should be used over a naked reinterpret_cast
* Note that it isn't guarantees all paths where slow recursive behaviour occurs has been addressed in this CL. Specifically, the InstancedStructDetails implementations to get the dispaly values still go through the data enumeration though it should be better now that GetInstancesNum and GetMemoryOfInstance are not recursively calling the Enumerate pathways. See GetDisplayValueString, GetTooltipText, GetDisplayValueIcon, GenerateStructPicker - this is the primary expense in Slate that is probably causing additional performance loss.
#rb mikko.mononen
#jira UE-207555
[CL 33319481 by logan buchy in ue5-main branch]
2024-04-30 01:05:58 -04:00
|
|
|
// The returned instances need to be compatible with base structure.
|
|
|
|
|
// This function returns empty instances in case they are not compatible, with the idea that we have as many instances as we have outer objects.
|
|
|
|
|
EnumerateInstances([&OutInstances, ExpectedBaseStructure](const UScriptStruct* ScriptStruct, uint8* Memory, UPackage* Package)
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr<FStructOnScope> Result;
|
|
|
|
|
|
|
|
|
|
if (ExpectedBaseStructure && ScriptStruct && ScriptStruct->IsChildOf(ExpectedBaseStructure))
|
|
|
|
|
{
|
|
|
|
|
Result = MakeShared<FStructOnScope>(ScriptStruct, Memory);
|
|
|
|
|
Result->SetPackage(Package);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OutInstances.Add(Result);
|
|
|
|
|
|
|
|
|
|
return true; // Continue
|
|
|
|
|
});
|
2023-04-27 10:06:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual bool IsPropertyIndirection() const override
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-23 07:53:34 -04:00
|
|
|
virtual uint8* GetValueBaseAddress(uint8* ParentValueAddress, const UStruct* ExpectedBaseStructure) const override
|
2023-04-27 10:06:33 -04:00
|
|
|
{
|
[Backout] - CL33242681
[FYI] marshall.beachy
Original CL Desc
-----------------------------------------------------------------
Performance improvement for nested FInstancedStruct in details panel
* Pathological performance issues occurred when viewing/editing USTRUCT objects that nested FInstancedStruct (either directly or in containers). The PropertyNode system's implementation when handling FInstancedStructs would recurse up the property tree to discover the address of the owning objects and then recurse again up with the starting address. The root cause of the performance issue is that the recursion would branch on the way up in the following functions: GetInstancesNum, GetMemoryOfInstance, GetOwnerPackages, HasValidStructData (by way of IsValid). On the way up again, branches would occur in GetValueBaseAddress, via duplicate calls in FItemPropertyNode::GetValueBaseAddress to ParentNode->GetValueAddress.
* The problem has been addressed by having the StructPropertyNode handle StructProviders that perform indirection differently than other providers. When indirection is present, walk up to the parent node of the struct which will be an ItemPropertyNode - the address of this is retrieved and passed back down to the StructProvider. In the current codebase, the FInstancedStructProvider is the only one that does this type of indirection - it can make the assumption that the passed in address is an FInstancedStruct and thus reinterpret it to get the StructMemory and UStruct* pointers.
* Added an InternalGetReadAddressUncached which public GetReadAddress** functions will call. This new function will not resolve the Struct in the case of a Struct property indirection unlike GetReadAddressUncached. It is intended as an internal function only to be called by the public APIs.
* Added a safeguard canary value to FInstancedStruct to debug/catch cases where the wrong raw void* is reinterpreted as an FInstancedStruct. FInstancedStruct::CastFromVoid helper should be used over a naked reinterpret_cast
* Note that it isn't guarantees all paths where slow recursive behaviour occurs has been addressed in this CL. Specifically, the InstancedStructDetails implementations to get the dispaly values still go through the data enumeration though it should be better now that GetInstancesNum and GetMemoryOfInstance are not recursively calling the Enumerate pathways. See GetDisplayValueString, GetTooltipText, GetDisplayValueIcon, GenerateStructPicker - this is the primary expense in Slate that is probably causing additional performance loss.
#rb mikko.mononen
#jira UE-207555
[CL 33319481 by logan buchy in ue5-main branch]
2024-04-30 01:05:58 -04:00
|
|
|
if (!ParentValueAddress)
|
2023-04-27 10:06:33 -04:00
|
|
|
{
|
[Backout] - CL33242681
[FYI] marshall.beachy
Original CL Desc
-----------------------------------------------------------------
Performance improvement for nested FInstancedStruct in details panel
* Pathological performance issues occurred when viewing/editing USTRUCT objects that nested FInstancedStruct (either directly or in containers). The PropertyNode system's implementation when handling FInstancedStructs would recurse up the property tree to discover the address of the owning objects and then recurse again up with the starting address. The root cause of the performance issue is that the recursion would branch on the way up in the following functions: GetInstancesNum, GetMemoryOfInstance, GetOwnerPackages, HasValidStructData (by way of IsValid). On the way up again, branches would occur in GetValueBaseAddress, via duplicate calls in FItemPropertyNode::GetValueBaseAddress to ParentNode->GetValueAddress.
* The problem has been addressed by having the StructPropertyNode handle StructProviders that perform indirection differently than other providers. When indirection is present, walk up to the parent node of the struct which will be an ItemPropertyNode - the address of this is retrieved and passed back down to the StructProvider. In the current codebase, the FInstancedStructProvider is the only one that does this type of indirection - it can make the assumption that the passed in address is an FInstancedStruct and thus reinterpret it to get the StructMemory and UStruct* pointers.
* Added an InternalGetReadAddressUncached which public GetReadAddress** functions will call. This new function will not resolve the Struct in the case of a Struct property indirection unlike GetReadAddressUncached. It is intended as an internal function only to be called by the public APIs.
* Added a safeguard canary value to FInstancedStruct to debug/catch cases where the wrong raw void* is reinterpreted as an FInstancedStruct. FInstancedStruct::CastFromVoid helper should be used over a naked reinterpret_cast
* Note that it isn't guarantees all paths where slow recursive behaviour occurs has been addressed in this CL. Specifically, the InstancedStructDetails implementations to get the dispaly values still go through the data enumeration though it should be better now that GetInstancesNum and GetMemoryOfInstance are not recursively calling the Enumerate pathways. See GetDisplayValueString, GetTooltipText, GetDisplayValueIcon, GenerateStructPicker - this is the primary expense in Slate that is probably causing additional performance loss.
#rb mikko.mononen
#jira UE-207555
[CL 33319481 by logan buchy in ue5-main branch]
2024-04-30 01:05:58 -04:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FInstancedStruct& InstancedStruct = *reinterpret_cast<FInstancedStruct*>(ParentValueAddress);
|
|
|
|
|
if (ExpectedBaseStructure && InstancedStruct.GetScriptStruct() && InstancedStruct.GetScriptStruct()->IsChildOf(ExpectedBaseStructure))
|
|
|
|
|
{
|
|
|
|
|
return InstancedStruct.GetMutableMemory();
|
2023-04-27 10:06:33 -04:00
|
|
|
}
|
2023-10-23 07:53:34 -04:00
|
|
|
|
2023-06-08 06:10:17 -04:00
|
|
|
return nullptr;
|
2023-04-27 10:06:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
|
|
|
|
|
void EnumerateInstances(TFunctionRef<bool(const UScriptStruct* ScriptStruct, uint8* Memory, UPackage* Package)> InFunc) const
|
|
|
|
|
{
|
|
|
|
|
if (!StructProperty.IsValid())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TArray<UPackage*> Packages;
|
|
|
|
|
StructProperty->GetOuterPackages(Packages);
|
|
|
|
|
|
|
|
|
|
StructProperty->EnumerateRawData([&InFunc, &Packages](void* RawData, const int32 DataIndex, const int32 /*NumDatas*/)
|
|
|
|
|
{
|
|
|
|
|
const UScriptStruct* ScriptStruct = nullptr;
|
|
|
|
|
uint8* Memory = nullptr;
|
|
|
|
|
UPackage* Package = nullptr;
|
|
|
|
|
if (FInstancedStruct* InstancedStruct = static_cast<FInstancedStruct*>(RawData))
|
|
|
|
|
{
|
|
|
|
|
ScriptStruct = InstancedStruct->GetScriptStruct();
|
|
|
|
|
Memory = InstancedStruct->GetMutableMemory();
|
|
|
|
|
if (ensureMsgf(Packages.IsValidIndex(DataIndex), TEXT("Expecting packges and raw data to match.")))
|
|
|
|
|
{
|
|
|
|
|
Package = Packages[DataIndex];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return InFunc(ScriptStruct, Memory, Package);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TSharedPtr<IPropertyHandle> StructProperty;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////
|
|
|
|
|
|
2021-04-20 02:43:47 -04:00
|
|
|
FInstancedStructDataDetails::FInstancedStructDataDetails(TSharedPtr<IPropertyHandle> InStructProperty)
|
|
|
|
|
{
|
|
|
|
|
#if DO_CHECK
|
|
|
|
|
FStructProperty* StructProp = CastFieldChecked<FStructProperty>(InStructProperty->GetProperty());
|
|
|
|
|
check(StructProp);
|
|
|
|
|
check(StructProp->Struct == FInstancedStruct::StaticStruct());
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
StructProperty = InStructProperty;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-26 12:46:32 -04:00
|
|
|
FInstancedStructDataDetails::~FInstancedStructDataDetails()
|
|
|
|
|
{
|
|
|
|
|
UE::StructUtils::Delegates::OnUserDefinedStructReinstanced.Remove(UserDefinedStructReinstancedHandle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FInstancedStructDataDetails::OnUserDefinedStructReinstancedHandle(const UUserDefinedStruct& Struct)
|
|
|
|
|
{
|
2023-10-13 13:10:37 -04:00
|
|
|
OnStructLayoutChanges();
|
2023-05-26 12:46:32 -04:00
|
|
|
}
|
|
|
|
|
|
2023-04-27 10:06:33 -04:00
|
|
|
void FInstancedStructDataDetails::SetOnRebuildChildren(FSimpleDelegate InOnRegenerateChildren)
|
2021-04-20 02:43:47 -04:00
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
OnRegenerateChildren = InOnRegenerateChildren;
|
2021-04-20 02:43:47 -04:00
|
|
|
}
|
|
|
|
|
|
2023-04-27 10:06:33 -04:00
|
|
|
TArray<TWeakObjectPtr<const UStruct>> FInstancedStructDataDetails::GetInstanceTypes() const
|
2021-04-20 02:43:47 -04:00
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
TArray<TWeakObjectPtr<const UStruct>> Result;
|
|
|
|
|
|
|
|
|
|
StructProperty->EnumerateConstRawData([&Result](const void* RawData, const int32 /*DataIndex*/, const int32 /*NumDatas*/)
|
2021-04-20 02:43:47 -04:00
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
TWeakObjectPtr<const UStruct>& Type = Result.AddDefaulted_GetRef();
|
|
|
|
|
if (const FInstancedStruct* InstancedStruct = static_cast<const FInstancedStruct*>(RawData))
|
2022-07-21 10:38:23 -04:00
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
Result.Add(InstancedStruct->GetScriptStruct());
|
2022-07-21 10:38:23 -04:00
|
|
|
}
|
2023-04-27 10:06:33 -04:00
|
|
|
else
|
2022-04-26 12:54:29 -04:00
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
Result.Add(nullptr);
|
2022-04-26 12:54:29 -04:00
|
|
|
}
|
2023-04-27 10:06:33 -04:00
|
|
|
return true;
|
|
|
|
|
});
|
2022-04-26 12:54:29 -04:00
|
|
|
|
2023-04-27 10:06:33 -04:00
|
|
|
return Result;
|
2021-04-20 02:43:47 -04:00
|
|
|
}
|
|
|
|
|
|
2024-05-24 16:10:25 -04:00
|
|
|
void FInstancedStructDataDetails::GetPropertyGroups(const TArray<TSharedPtr<IPropertyHandle>>& InProperties, IDetailChildrenBuilder& InChildBuilder, TMap<TSharedPtr<IPropertyHandle>, IDetailGroup*>& OutPropertyToGroup) const
|
|
|
|
|
{
|
|
|
|
|
static const FName CategoryName = FName(TEXT("Category"));
|
|
|
|
|
static const FName EnableCategoriesName = FName(TEXT("EnableCategories"));
|
|
|
|
|
|
|
|
|
|
// Temporarily store a mapping of category -> group while groups are being built
|
|
|
|
|
TMap<FString, IDetailGroup*> CategoryToGroup;
|
|
|
|
|
|
|
|
|
|
for (const TSharedPtr<IPropertyHandle>& PropertyHandle : InProperties)
|
|
|
|
|
{
|
|
|
|
|
// The property needs the "EnableCategories" metadata in order to be added under a group. Grouping is opt-in.
|
|
|
|
|
if (!PropertyHandle->HasMetaData(EnableCategoriesName))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FString& PropertyCategory = PropertyHandle->GetMetaData(CategoryName).TrimStartAndEnd();
|
|
|
|
|
if (PropertyCategory.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr bool bCullEmpty = true;
|
|
|
|
|
TArray<FString> CategoriesToAdd;
|
|
|
|
|
PropertyCategory.ParseIntoArray(CategoriesToAdd, TEXT("|"), bCullEmpty);
|
|
|
|
|
|
|
|
|
|
if (CategoriesToAdd.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tracks the category name as it is being built up (eg, Foo -> Foo|Bar -> Foo|Bar|Baz)
|
|
|
|
|
FString CompleteCategory;
|
|
|
|
|
|
|
|
|
|
// For this property, add all of the groups needed for its category (eg, Foo, Foo|Bar, and Foo|Bar|Baz)
|
|
|
|
|
IDetailGroup* CurrentGroup = nullptr;
|
|
|
|
|
for (FString& CategoryToAdd : CategoriesToAdd)
|
|
|
|
|
{
|
|
|
|
|
CategoryToAdd.TrimStartAndEndInline();
|
|
|
|
|
|
|
|
|
|
// Cover the edge case where there's a category like "Foo|", which is invalid
|
|
|
|
|
CategoryToAdd.TrimCharInline(TEXT('|'), nullptr);
|
|
|
|
|
|
|
|
|
|
CompleteCategory = CompleteCategory.IsEmpty()
|
|
|
|
|
? CategoryToAdd
|
|
|
|
|
: FString::Join(TArray({CompleteCategory, CategoryToAdd}), TEXT("|"));
|
|
|
|
|
|
|
|
|
|
// Create the category's group if it has not yet been created
|
|
|
|
|
if (!CategoryToGroup.Contains(CompleteCategory))
|
|
|
|
|
{
|
|
|
|
|
if (CurrentGroup)
|
|
|
|
|
{
|
|
|
|
|
// Add the group to the previous group if this is a nested category (eg, if this is the Foo|Bar group, add to the Foo group)
|
|
|
|
|
CurrentGroup = &CurrentGroup->AddGroup(FName(CompleteCategory), FText::FromString(CategoryToAdd));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Otherwise, add the group as a normal group via the builder
|
|
|
|
|
CurrentGroup = &InChildBuilder.AddGroup(FName(CompleteCategory), FText::FromString(CategoryToAdd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CategoryToGroup.Add(CompleteCategory, CurrentGroup);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CurrentGroup = CategoryToGroup[CompleteCategory];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
check(CurrentGroup);
|
|
|
|
|
OutPropertyToGroup.Add(PropertyHandle, CurrentGroup);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-13 13:10:37 -04:00
|
|
|
void FInstancedStructDataDetails::OnStructLayoutChanges()
|
|
|
|
|
{
|
|
|
|
|
if (StructProvider.IsValid())
|
|
|
|
|
{
|
|
|
|
|
// Reset the struct provider immediately, some update functions might get called with the old struct.
|
|
|
|
|
StructProvider->Reset();
|
|
|
|
|
}
|
|
|
|
|
OnRegenerateChildren.ExecuteIfBound();
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-26 17:30:43 -05:00
|
|
|
void FInstancedStructDataDetails::OnStructHandlePostChange()
|
|
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
if (StructProvider.IsValid())
|
2022-01-26 17:30:43 -05:00
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
TArray<TWeakObjectPtr<const UStruct>> InstanceTypes = GetInstanceTypes();
|
|
|
|
|
if (InstanceTypes != CachedInstanceTypes)
|
2022-01-26 17:30:43 -05:00
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
OnRegenerateChildren.ExecuteIfBound();
|
|
|
|
|
}
|
2022-01-26 17:30:43 -05:00
|
|
|
}
|
2021-04-20 02:43:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FInstancedStructDataDetails::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow)
|
|
|
|
|
{
|
2022-01-26 17:30:43 -05:00
|
|
|
StructProperty->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FInstancedStructDataDetails::OnStructHandlePostChange));
|
2023-05-26 12:46:32 -04:00
|
|
|
if (!UserDefinedStructReinstancedHandle.IsValid())
|
|
|
|
|
{
|
|
|
|
|
UserDefinedStructReinstancedHandle = UE::StructUtils::Delegates::OnUserDefinedStructReinstanced.AddSP(this, &FInstancedStructDataDetails::OnUserDefinedStructReinstancedHandle);
|
|
|
|
|
}
|
2021-04-20 02:43:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FInstancedStructDataDetails::GenerateChildContent(IDetailChildrenBuilder& ChildBuilder)
|
|
|
|
|
{
|
2022-01-26 17:30:43 -05:00
|
|
|
// Add the rows for the struct
|
2023-04-27 10:06:33 -04:00
|
|
|
TSharedRef<FInstancedStructProvider> NewStructProvider = MakeShared<FInstancedStructProvider>(StructProperty);
|
2024-04-19 14:37:05 -04:00
|
|
|
|
|
|
|
|
bool bCustomizedProperty = false;
|
|
|
|
|
|
|
|
|
|
const UStruct* BaseStruct = NewStructProvider->GetBaseStructure();
|
|
|
|
|
if (BaseStruct)
|
2021-04-20 02:43:47 -04:00
|
|
|
{
|
2024-04-19 14:37:05 -04:00
|
|
|
static const FName EditModuleName("PropertyEditor");
|
|
|
|
|
if (const FPropertyEditorModule* EditModule = FModuleManager::GetModulePtr<FPropertyEditorModule>(EditModuleName))
|
|
|
|
|
{
|
|
|
|
|
if (EditModule->IsCustomizedStruct(BaseStruct, FCustomPropertyTypeLayoutMap()))
|
|
|
|
|
{
|
|
|
|
|
bCustomizedProperty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bCustomizedProperty)
|
|
|
|
|
{
|
|
|
|
|
// Use the struct name instead of the fully-qualified property name
|
|
|
|
|
const FText Label = BaseStruct->GetDisplayNameText();
|
|
|
|
|
const FName PropertyName = StructProperty->GetProperty()->GetFName();
|
|
|
|
|
|
|
|
|
|
// If the struct has a property customization, then we'll route through AddChildStructure, as it supports
|
|
|
|
|
// IPropertyTypeCustomization. The other branch is mostly kept as-is for legacy support purposes.
|
|
|
|
|
IDetailPropertyRow* PropertyRow = ChildBuilder.AddChildStructure(
|
|
|
|
|
StructProperty.ToSharedRef(), NewStructProvider, PropertyName, Label
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Expansion state is not properly persisted for these structures, so let's expanded it by default for now
|
|
|
|
|
if (PropertyRow)
|
|
|
|
|
{
|
|
|
|
|
PropertyRow->ShouldAutoExpand(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
TArray<TSharedPtr<IPropertyHandle>> ChildProperties = StructProperty->AddChildStructure(NewStructProvider);
|
2024-05-24 16:10:25 -04:00
|
|
|
|
|
|
|
|
// Properties may have Category metadata. If that's the case, they should be added under groups.
|
|
|
|
|
TMap<TSharedPtr<IPropertyHandle>, IDetailGroup*> PropertyToGroup;
|
|
|
|
|
GetPropertyGroups(ChildProperties, ChildBuilder, PropertyToGroup);
|
|
|
|
|
|
2024-04-19 14:37:05 -04:00
|
|
|
for (TSharedPtr<IPropertyHandle> ChildHandle : ChildProperties)
|
|
|
|
|
{
|
2024-05-24 16:10:25 -04:00
|
|
|
// If the property has a group, add it under the group. Otherwise, just add it normally via the builder.
|
|
|
|
|
IDetailGroup** PropertyGroup = PropertyToGroup.Find(ChildHandle);
|
|
|
|
|
if (PropertyGroup && *PropertyGroup)
|
|
|
|
|
{
|
|
|
|
|
IDetailPropertyRow& Row = (*PropertyGroup)->AddPropertyRow(ChildHandle.ToSharedRef());
|
|
|
|
|
OnChildRowAdded(Row);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
IDetailPropertyRow& Row = ChildBuilder.AddProperty(ChildHandle.ToSharedRef());
|
|
|
|
|
OnChildRowAdded(Row);
|
|
|
|
|
}
|
2024-04-19 14:37:05 -04:00
|
|
|
}
|
2021-04-20 02:43:47 -04:00
|
|
|
}
|
2023-04-27 10:06:33 -04:00
|
|
|
|
|
|
|
|
StructProvider = NewStructProvider;
|
|
|
|
|
|
|
|
|
|
CachedInstanceTypes = GetInstanceTypes();
|
2021-04-20 02:43:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FInstancedStructDataDetails::Tick(float DeltaTime)
|
|
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
// If the instance types change (e.g. due to selecting new struct type), we'll need to update the layout.
|
|
|
|
|
TArray<TWeakObjectPtr<const UStruct>> InstanceTypes = GetInstanceTypes();
|
|
|
|
|
if (InstanceTypes != CachedInstanceTypes)
|
2021-04-20 02:43:47 -04:00
|
|
|
{
|
2023-04-27 10:06:33 -04:00
|
|
|
OnRegenerateChildren.ExecuteIfBound();
|
2021-04-20 02:43:47 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FName FInstancedStructDataDetails::GetName() const
|
|
|
|
|
{
|
|
|
|
|
static const FName Name("InstancedStructDataDetails");
|
|
|
|
|
return Name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
TSharedRef<IPropertyTypeCustomization> FInstancedStructDetails::MakeInstance()
|
|
|
|
|
{
|
2022-01-26 17:30:43 -05:00
|
|
|
return MakeShared<FInstancedStructDetails>();
|
2021-04-20 02:43:47 -04:00
|
|
|
}
|
|
|
|
|
|
2023-04-18 04:45:22 -04:00
|
|
|
FInstancedStructDetails::~FInstancedStructDetails()
|
|
|
|
|
{
|
|
|
|
|
if (OnObjectsReinstancedHandle.IsValid())
|
|
|
|
|
{
|
|
|
|
|
FCoreUObjectDelegates::OnObjectsReinstanced.Remove(OnObjectsReinstancedHandle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-20 02:43:47 -04:00
|
|
|
void FInstancedStructDetails::CustomizeHeader(TSharedRef<class IPropertyHandle> StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
|
|
|
|
|
{
|
|
|
|
|
StructProperty = StructPropertyHandle;
|
2023-04-18 04:45:22 -04:00
|
|
|
PropUtils = StructCustomizationUtils.GetPropertyUtilities();
|
2021-04-20 02:43:47 -04:00
|
|
|
|
2023-04-18 04:45:22 -04:00
|
|
|
OnObjectsReinstancedHandle = FCoreUObjectDelegates::OnObjectsReinstanced.AddSP(this, &FInstancedStructDetails::OnObjectsReinstanced);
|
2021-10-27 05:49:26 -04:00
|
|
|
|
2021-04-20 02:43:47 -04:00
|
|
|
HeaderRow
|
2024-04-19 14:52:27 -04:00
|
|
|
.ShouldAutoExpand(true)
|
2021-04-20 02:43:47 -04:00
|
|
|
.NameContent()
|
|
|
|
|
[
|
|
|
|
|
StructPropertyHandle->CreatePropertyNameWidget()
|
|
|
|
|
]
|
|
|
|
|
.ValueContent()
|
|
|
|
|
.MinDesiredWidth(250.f)
|
|
|
|
|
.VAlign(VAlign_Center)
|
|
|
|
|
[
|
2024-06-03 16:11:51 -04:00
|
|
|
SAssignNew(StructPicker, SInstancedStructPicker, StructProperty, PropUtils)
|
2024-03-26 11:18:05 -04:00
|
|
|
]
|
|
|
|
|
.IsEnabled(StructProperty->IsEditable());
|
2021-04-20 02:43:47 -04:00
|
|
|
}
|
|
|
|
|
|
2023-04-18 04:45:22 -04:00
|
|
|
void FInstancedStructDetails::OnObjectsReinstanced(const FReplacementObjectMap& ObjectMap)
|
|
|
|
|
{
|
|
|
|
|
// Force update the details when BP is compiled, since we may cached hold references to the old object or class.
|
2023-04-20 19:07:04 -04:00
|
|
|
if (!ObjectMap.IsEmpty() && PropUtils.IsValid())
|
2023-04-18 04:45:22 -04:00
|
|
|
{
|
2023-04-20 19:07:04 -04:00
|
|
|
PropUtils->RequestRefresh();
|
2023-04-18 04:45:22 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-20 02:43:47 -04:00
|
|
|
void FInstancedStructDetails::CustomizeChildren(TSharedRef<class IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
|
|
|
|
|
{
|
2022-01-26 17:30:43 -05:00
|
|
|
TSharedRef<FInstancedStructDataDetails> DataDetails = MakeShared<FInstancedStructDataDetails>(StructProperty);
|
2021-04-20 02:43:47 -04:00
|
|
|
StructBuilder.AddCustomBuilder(DataDetails);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|