// Copyright Epic Games, Inc. All Rights Reserved. #include "StructurePropertyNode.h" #include "ItemPropertyNode.h" #include "PropertyEditorHelpers.h" void FStructurePropertyNode::InitChildNodes() { InternalInitChildNodes(FName()); } void FStructurePropertyNode::InternalInitChildNodes(FName SinglePropertyName) { const bool bShouldShowHiddenProperties = !!HasNodeFlags(EPropertyNodeFlags::ShouldShowHiddenProperties); const bool bShouldShowDisableEditOnInstance = !!HasNodeFlags(EPropertyNodeFlags::ShouldShowDisableEditOnInstance); const UStruct* Struct = GetBaseStructure(); TArray StructMembers; for (TFieldIterator It(Struct); It; ++It) { FProperty* StructMember = *It; if (PropertyEditorHelpers::ShouldBeVisible(*this, StructMember)) { if (SinglePropertyName == NAME_None || StructMember->GetFName() == SinglePropertyName) { StructMembers.Add(StructMember); if (SinglePropertyName != NAME_None) { break; } } } } // Cache the init time base struct so that we can determine if the struct has changed. // Store the cached base struct before calling AddChildNode() as they may call back to this node. WeakCachedBaseStruct = Struct; PropertyEditorHelpers::OrderPropertiesFromMetadata(StructMembers); for (FProperty* StructMember : StructMembers) { TSharedPtr NewItemNode(new FItemPropertyNode); FPropertyNodeInitParams InitParams; InitParams.ParentNode = SharedThis(this); InitParams.Property = StructMember; InitParams.ArrayOffset = 0; InitParams.ArrayIndex = INDEX_NONE; InitParams.bAllowChildren = SinglePropertyName == NAME_None; InitParams.bForceHiddenPropertyVisibility = bShouldShowHiddenProperties; InitParams.bCreateDisableEditOnInstanceNodes = bShouldShowDisableEditOnInstance; InitParams.bCreateCategoryNodes = false; NewItemNode->InitNode(InitParams); AddChildNode(NewItemNode); } } void FStructurePropertyNode::InitBeforeNodeFlags() { // Cache the base struct. It is used to check if the struct changes later. // The struct will be cached on each call to InternalInitChildNodes() as well. // We'll cache it here too, so that a FStructurePropertyNode which is initialized // with "InitParams.bAllowChildren = false" has it properly set up. WeakCachedBaseStruct = GetBaseStructure(); } bool FStructurePropertyNode::GetReadAddressUncached(const FPropertyNode& InPropertyNode, FReadAddressListData& OutAddresses) const { if (!HasValidStructData()) { return false; } check(StructProvider.IsValid()); const FProperty* InItemProperty = InPropertyNode.GetProperty(); if (!InItemProperty) { return false; } UStruct* OwnerStruct = InItemProperty->GetOwnerStruct(); if (!OwnerStruct || OwnerStruct->IsStructTrashed()) { // Verify that the property is not part of an invalid trash class return false; } TArray> Instances; StructProvider->GetInstances(Instances, WeakCachedBaseStruct.Get()); bool bHasData = false; for (TSharedPtr& Instance : Instances) { uint8* ReadAddress = Instance.IsValid() ? Instance->GetStructMemory() : nullptr; if (ReadAddress) { OutAddresses.Add(nullptr, InPropertyNode.GetValueBaseAddress(ReadAddress, InPropertyNode.HasNodeFlags(EPropertyNodeFlags::IsSparseClassData) != 0, /*bIsStruct=*/true), /*bIsStruct=*/true); bHasData = true; } } return bHasData; } bool FStructurePropertyNode::GetReadAddressUncached(const FPropertyNode& InPropertyNode, bool InRequiresSingleSelection, FReadAddressListData* OutAddresses, bool bComparePropertyContents, bool bObjectForceCompare, bool bArrayPropertiesCanDifferInSize) const { if (!HasValidStructData()) { return false; } check(StructProvider.IsValid()); const FProperty* InItemProperty = InPropertyNode.GetProperty(); if (!InItemProperty) { return false; } const UStruct* OwnerStruct = InItemProperty->GetOwnerStruct(); if (!OwnerStruct || OwnerStruct->IsStructTrashed()) { // Verify that the property is not part of an invalid trash class return false; } bool bAllTheSame = true; TArray> Instances; StructProvider->GetInstances(Instances, WeakCachedBaseStruct.Get()); if (Instances.IsEmpty()) { return false; } if (bComparePropertyContents || bObjectForceCompare) { const bool bIsSparse = InPropertyNode.HasNodeFlags(EPropertyNodeFlags::IsSparseClassData) != 0; const uint8* BaseAddress = nullptr; const UStruct* BaseStruct = nullptr; for (TSharedPtr& Instance : Instances) { if (Instance.IsValid()) { if (const UStruct* Struct = Instance->GetStruct()) { if (const uint8* ReadAddress = InPropertyNode.GetValueBaseAddress(Instance->GetStructMemory(), bIsSparse, /*bIsStruct=*/true)) { if (!BaseAddress) { BaseAddress = ReadAddress; BaseStruct = Struct; } else { if (BaseStruct != Struct) { bAllTheSame = false; break; } if (!InItemProperty->Identical(BaseAddress, ReadAddress)) { bAllTheSame = false; break; } } } } } } // If none of the instances have data, treat it as if the instance data was empty. if (!BaseStruct) { bAllTheSame = false; } } else { // Check that all are valid or invalid. const UStruct* BaseStruct = Instances[0].IsValid() ? Instances[0]->GetStruct() : nullptr; for (int32 Index = 1; Index < Instances.Num(); Index++) { const UStruct* Struct = Instances[Index].IsValid() ? Instances[Index]->GetStruct() : nullptr; if (BaseStruct != Struct) { bAllTheSame = false; break; } } // If none of the instances have data, treat it as if the instance data was empty. if (!BaseStruct) { bAllTheSame = false; } } if (OutAddresses) { for (TSharedPtr& Instance : Instances) { uint8* ReadAddress = Instance.IsValid() ? Instance->GetStructMemory() : nullptr; if (ReadAddress) { OutAddresses->Add(nullptr, InPropertyNode.GetValueBaseAddress(ReadAddress, InPropertyNode.HasNodeFlags(EPropertyNodeFlags::IsSparseClassData) != 0, /*bIsStruct=*/true), /*bIsStruct=*/true); } } } return bAllTheSame; } uint8* FStructurePropertyNode::GetValueBaseAddress(uint8* StartAddress, bool bIsSparseData, bool bIsStruct) const { // If called with struct data, we expect that it is compatible with the first structure node down in the property node chain, return the address as is. // This gets called usually when the calling code is dealing with a parent complex node. if (bIsStruct) { return StartAddress; } if (StructProvider) { // Assume that this code gets called with an object or object sparse data. // // The passed object might not be the one that contains the values provided by struct provider. // For example this function might get called on an edited object's template object. // In that case the data structure is expected to match between the data edited by this node and the foreign object. // If the structure provider is set up as indirection, then it knows how to translate parent node's value address to // new value address even on data that is not the same as in the structure provider. if (StructProvider->IsPropertyIndirection()) { const TSharedPtr ParentNode = ParentNodeWeakPtr.Pin(); if (!ensureMsgf(ParentNode, TEXT("Expecting valid parent node when indirection structure provider is called with Object data."))) { return nullptr; } // Resolve from parent nodes data. uint8* ParentValueAddress = ParentNode->GetValueAddress(StartAddress, bIsSparseData); uint8* ValueAddress = StructProvider->GetValueBaseAddress(ParentValueAddress, WeakCachedBaseStruct.Get()); return ValueAddress; } // The struct is really standalone, in which case we always return the standalone struct data. // In that case we can only support one instance, since we cannot discern them. // Note: Multiple standalone structure instances are supported when bIsStruct is true (e.g. when the structure property is root node). TArray> Instances; StructProvider->GetInstances(Instances, WeakCachedBaseStruct.Get()); ensureMsgf(Instances.Num() <= 1, TEXT("Expecting max one instance on standalone structure provider.")); if (Instances.Num() == 1 && Instances[0].IsValid()) { return Instances[0]->GetStructMemory(); } } return nullptr; } TSharedPtr FStructurePropertyNode::GenerateSingleChild(FName ChildPropertyName) { constexpr bool bDestroySelf = false; DestroyTree(bDestroySelf); // No category nodes should be created in single property mode SetNodeFlags(EPropertyNodeFlags::ShowCategories, false); InternalInitChildNodes(ChildPropertyName); if (ChildNodes.Num() > 0) { // only one node should be been created check(ChildNodes.Num() == 1); return ChildNodes[0]; } return nullptr; } EPropertyDataValidationResult FStructurePropertyNode::EnsureDataIsValid() { CachedReadAddresses.Reset(); // If the struct has changed, rebuild children. const UStruct* CachedBaseStruct = WeakCachedBaseStruct.Get(); if (GetBaseStructure() != CachedBaseStruct) { RebuildChildren(); // Invalidate the object to prevent EditCondition evaluating on old nodes. return EPropertyDataValidationResult::ObjectInvalid; } return FPropertyNode::EnsureDataIsValid(); }