Files
UnrealEngineUWP/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeStruct.cpp
dave jones2 d9397ab592 UE-172222 - Improve redirect support for Blueprints. (resubmit)
A reflected Blueprint type can now be changed without breaking existing content, so long as an autocast function exists for the two types.

Previously, pin reconstruction would give up if two pin types didn't match exactly, which resulted in orphaned pins and compilation errors. Now, it'll first try to find an autocast function for the two types, and insert a cast node into the graph if one is found.

Additionally, notes will be added whenever a new cast node was added, which serves as a notice that users might want to update their API to use the new type change.

#jira UE-172222
#preflight 63d85cf57a39a1802189bdda
#rb phillip.kavan

[CL 24029327 by dave jones2 in ue5-main branch]
2023-02-06 09:08:45 -05:00

498 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "K2Node_MakeStruct.h"
#include "Containers/Array.h"
#include "Containers/EnumAsByte.h"
#include "Containers/Map.h"
#include "Containers/UnrealString.h"
#include "Delegates/Delegate.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraphSchema_K2.h"
#include "EditorCategoryUtils.h"
#include "Engine/Blueprint.h"
#include "Internationalization/Internationalization.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/CompilerResultsLog.h"
#include "KismetCompilerMisc.h"
#include "MakeStructHandler.h"
#include "Math/Rotator.h"
#include "Math/UnrealMathSSE.h"
#include "Math/Vector2D.h"
#include "Misc/AssertionMacros.h"
#include "PropertyCustomizationHelpers.h"
#include "Serialization/Archive.h"
#include "Styling/AppStyle.h"
#include "UObject/Class.h"
#include "UObject/ObjectPtr.h"
#include "UObject/ObjectSaveContext.h"
#include "UObject/StructOnScope.h"
#include "UObject/TextProperty.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakObjectPtrTemplates.h"
class FKismetCompilerContext;
#define LOCTEXT_NAMESPACE "K2Node_MakeStruct"
//////////////////////////////////////////////////////////////////////////
// UK2Node_MakeStruct
UK2Node_MakeStruct::FMakeStructPinManager::FMakeStructPinManager(const uint8* InSampleStructMemory, UBlueprint* InOwningBP)
: FStructOperationOptionalPinManager()
, SampleStructMemory(InSampleStructMemory)
, OwningBP(InOwningBP)
, bHasAdvancedPins(false)
{
}
void UK2Node_MakeStruct::FMakeStructPinManager::GetRecordDefaults(FProperty* TestProperty, FOptionalPinFromProperty& Record) const
{
UK2Node_StructOperation::FStructOperationOptionalPinManager::GetRecordDefaults(TestProperty, Record);
Record.bIsMarkedForAdvancedDisplay = TestProperty ? TestProperty->HasAnyPropertyFlags(CPF_AdvancedDisplay) : false;
bHasAdvancedPins |= Record.bIsMarkedForAdvancedDisplay;
}
void UK2Node_MakeStruct::FMakeStructPinManager::CustomizePinData(UEdGraphPin* Pin, FName SourcePropertyName, int32 ArrayIndex, FProperty* Property) const
{
UK2Node_StructOperation::FStructOperationOptionalPinManager::CustomizePinData(Pin, SourcePropertyName, ArrayIndex, Property);
if (Pin && Property)
{
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
check(Schema);
// Should pin default value be filled as FText?
const bool bIsText = Property->IsA<FTextProperty>();
checkSlow(bIsText == ((UEdGraphSchema_K2::PC_Text == Pin->PinType.PinCategory) && !Pin->PinType.IsContainer()));
const bool bIsObject = Property->IsA<FObjectPropertyBase>();
checkSlow(bIsObject == ((UEdGraphSchema_K2::PC_Object == Pin->PinType.PinCategory || UEdGraphSchema_K2::PC_Class == Pin->PinType.PinCategory ||
UEdGraphSchema_K2::PC_SoftObject == Pin->PinType.PinCategory || UEdGraphSchema_K2::PC_SoftClass == Pin->PinType.PinCategory) && !Pin->PinType.IsContainer()));
if (Property->HasAnyPropertyFlags(CPF_AdvancedDisplay))
{
Pin->bAdvancedView = true;
bHasAdvancedPins = true;
}
const FString& MetadataDefaultValue = Property->GetMetaData(TEXT("MakeStructureDefaultValue"));
if (!MetadataDefaultValue.IsEmpty())
{
Schema->SetPinAutogeneratedDefaultValue(Pin, MetadataDefaultValue);
return;
}
if (nullptr != SampleStructMemory)
{
FString NewDefaultValue;
if (FBlueprintEditorUtils::PropertyValueToString(Property, SampleStructMemory, NewDefaultValue))
{
if (Schema->IsPinDefaultValid(Pin, NewDefaultValue, nullptr, FText::GetEmpty()).IsEmpty())
{
Schema->SetPinAutogeneratedDefaultValue(Pin, NewDefaultValue);
return;
}
}
}
Schema->SetPinAutogeneratedDefaultValueBasedOnType(Pin);
}
}
static bool CanBeExposed(const FProperty* Property, UBlueprint* BP)
{
if (Property)
{
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
check(Schema);
// Treat all inline edit condition properties as override flags; that is, don't allow
// these to be exposed as part of the optional input pin set. Their value will be set
// implicitly at runtime based on whether or not any bound members are exposed, rather
// than explicitly via an exposed input pin. This emulates how the Property Editor
// handles setting these values at edit time (they appear as an inline checkbox that
// the user ticks on to set the flag and enable/override a bound property's value).
static const FName MD_InlineEditConditionToggle(TEXT("InlineEditConditionToggle"));
if (Property->HasMetaData(MD_InlineEditConditionToggle))
{
return false;
}
const bool bIsEditorBP = IsEditorOnlyObject(BP);
const bool bIsEditAnywhereProperty = Property->HasAllPropertyFlags(CPF_Edit) &&
!Property->HasAnyPropertyFlags(CPF_EditConst);
if (!Property->HasAllPropertyFlags(CPF_BlueprintReadOnly) ||
(bIsEditorBP && bIsEditAnywhereProperty) )
{
if (Property->HasAllPropertyFlags(CPF_BlueprintVisible) && !(Property->ArrayDim > 1))
{
FEdGraphPinType DumbGraphPinType;
if (Schema->ConvertPropertyToPinType(Property, /*out*/ DumbGraphPinType))
{
return true;
}
}
}
}
return false;
}
bool UK2Node_MakeStruct::FMakeStructPinManager::CanTreatPropertyAsOptional(FProperty* TestProperty) const
{
return CanBeExposed(TestProperty, OwningBP);
}
UK2Node_MakeStruct::UK2Node_MakeStruct(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bMadeAfterOverridePinRemoval(false)
{
}
void UK2Node_MakeStruct::AllocateDefaultPins()
{
if (StructType)
{
PreloadObject(StructType);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Struct, StructType, StructType->GetFName());
bool bHasAdvancedPins = false;
{
FStructOnScope StructOnScope(StructType);
FMakeStructPinManager OptionalPinManager(StructOnScope.GetStructMemory(), GetBlueprint());
OptionalPinManager.RebuildPropertyList(ShowPinForProperties, StructType);
OptionalPinManager.CreateVisiblePins(ShowPinForProperties, StructType, EGPD_Input, this);
bHasAdvancedPins = OptionalPinManager.HasAdvancedPins();
}
// Set container pin types to have their default values ignored, which will in turn
// enable auto generation for any that are not set by the user.
for(UEdGraphPin* Pin : Pins)
{
Pin->bDefaultValueIsIgnored = Pin->bDefaultValueIsIgnored || Pin->PinType.IsContainer();
}
// When struct has a lot of fields, mark their pins as advanced
if(!bHasAdvancedPins && Pins.Num() > 5)
{
for(int32 PinIndex = 3; PinIndex < Pins.Num(); ++PinIndex)
{
if(UEdGraphPin * EdGraphPin = Pins[PinIndex])
{
EdGraphPin->bAdvancedView = true;
bHasAdvancedPins = true;
}
}
}
if (bHasAdvancedPins && (ENodeAdvancedPins::NoPins == AdvancedPinDisplay))
{
AdvancedPinDisplay = ENodeAdvancedPins::Hidden;
}
}
}
void UK2Node_MakeStruct::PreloadRequiredAssets()
{
PreloadObject(StructType);
Super::PreloadRequiredAssets();
}
void UK2Node_MakeStruct::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const
{
Super::ValidateNodeDuringCompilation(MessageLog);
if(!StructType)
{
MessageLog.Error(*LOCTEXT("NoStruct_Error", "No Struct in @@").ToString(), this);
}
else
{
UBlueprint* BP = GetBlueprint();
for (TFieldIterator<FProperty> It(StructType); It; ++It)
{
const FProperty* Property = *It;
if (CanBeExposed(Property, BP))
{
if (Property->ArrayDim > 1)
{
const UEdGraphPin* Pin = FindPin(Property->GetFName());
MessageLog.Warning(*LOCTEXT("StaticArray_Warning", "@@ - the native property is a static array, which is not supported by blueprints").ToString(), Pin);
}
}
}
if (!bMadeAfterOverridePinRemoval)
{
MessageLog.Note(*NSLOCTEXT("K2Node", "OverridePinRemoval_SetFieldsInStruct", "Override pins have been removed from @@ in @@, it functions the same as it did but some functionality may be deprecated! This note will go away after you resave the asset!").ToString(), this, GetBlueprint());
}
}
}
FText UK2Node_MakeStruct::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if (StructType == nullptr)
{
return LOCTEXT("MakeNullStructTitle", "Make <unknown struct>");
}
else if (CachedNodeTitle.IsOutOfDate(this))
{
FFormatNamedArguments Args;
Args.Add(TEXT("StructName"), FText::FromName(StructType->GetFName()));
// FText::Format() is slow, so we cache this to save on performance
CachedNodeTitle.SetCachedText(FText::Format(LOCTEXT("MakeNodeTitle", "Make {StructName}"), Args), this);
}
return CachedNodeTitle;
}
FText UK2Node_MakeStruct::GetTooltipText() const
{
if (StructType == nullptr)
{
return LOCTEXT("MakeNullStruct_Tooltip", "Adds a node that create an '<unknown struct>' from its members");
}
else if (CachedTooltip.IsOutOfDate(this))
{
// FText::Format() is slow, so we cache this to save on performance
CachedTooltip.SetCachedText(FText::Format(
LOCTEXT("MakeStruct_Tooltip", "Adds a node that create a '{0}' from its members"),
FText::FromName(StructType->GetFName())
), this);
}
return CachedTooltip;
}
FSlateIcon UK2Node_MakeStruct::GetIconAndTint(FLinearColor& OutColor) const
{
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.MakeStruct_16x");
return Icon;
}
FLinearColor UK2Node_MakeStruct::GetNodeTitleColor() const
{
if(const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>())
{
FEdGraphPinType PinType;
PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
PinType.PinSubCategoryObject = StructType;
return K2Schema->GetPinTypeColor(PinType);
}
return UK2Node::GetNodeTitleColor();
}
bool UK2Node_MakeStruct::CanBeMade(const UScriptStruct* Struct, const bool bForInternalUse)
{
return (Struct && !Struct->HasMetaData(FBlueprintMetadata::MD_NativeMakeFunction) && UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct, bForInternalUse));
}
bool UK2Node_MakeStruct::CanBeSplit(const UScriptStruct* Struct, UBlueprint* InBP)
{
if (CanBeMade(Struct))
{
for (TFieldIterator<FProperty> It(Struct); It; ++It)
{
if (CanBeExposed(*It, InBP))
{
return true;
}
}
}
return false;
}
FNodeHandlingFunctor* UK2Node_MakeStruct::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
return new FKCHandler_MakeStruct(CompilerContext);
}
UK2Node::ERedirectType UK2Node_MakeStruct::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
{
ERedirectType Result = UK2Node::DoPinsMatchForReconstruction(NewPin, NewPinIndex, OldPin, OldPinIndex);
if ((ERedirectType_None == Result) && DoRenamedPinsMatch(NewPin, OldPin, false))
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
check(K2Schema);
const UClass* CallingContext = GetBlueprint()->GeneratedClass;
check(CallingContext);
const bool bPinsAreTypeCompatible = K2Schema->ArePinTypesCompatible(OldPin->PinType, NewPin->PinType, CallingContext);
if (bPinsAreTypeCompatible)
{
Result = ERedirectType_Name;
}
else
{
if (K2Schema->SearchForAutocastFunction(OldPin->PinType, NewPin->PinType).IsSet())
{
Result = static_cast<ERedirectType>(static_cast<uint8>(ERedirectType_Name) | static_cast<uint8>(ERedirectType_Type));
}
else
{
Result = ERedirectType_None;
}
}
}
return Result;
}
void UK2Node_MakeStruct::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
Super::SetupMenuActions(ActionRegistrar, FMakeStructSpawnerAllowedDelegate::CreateStatic(&UK2Node_MakeStruct::CanBeMade), EGPD_Input);
}
FText UK2Node_MakeStruct::GetMenuCategory() const
{
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Struct);
}
void UK2Node_MakeStruct::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(this);
if (Blueprint && !Blueprint->bBeingCompiled)
{
bMadeAfterOverridePinRemoval = true;
}
}
void UK2Node_MakeStruct::PostPlacedNewNode()
{
Super::PostPlacedNewNode();
// New nodes automatically have this set.
bMadeAfterOverridePinRemoval = true;
}
void UK2Node_MakeStruct::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
if (Ar.IsLoading() && !Ar.IsTransacting() && !HasAllFlags(RF_Transient))
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(this);
if (Blueprint && !bMadeAfterOverridePinRemoval)
{
// Check if this node actually requires warning the user that functionality has changed.
bMadeAfterOverridePinRemoval = true;
if (StructType != nullptr)
{
FOptionalPinManager PinManager;
// Have to check if this node is even in danger.
for (FOptionalPinFromProperty& PropertyEntry : ShowPinForProperties)
{
FProperty* Property = StructType->FindPropertyByName(PropertyEntry.PropertyName);
bool bNegate = false;
if (FProperty* OverrideProperty = PropertyCustomizationHelpers::GetEditConditionProperty(Property, bNegate))
{
bool bHadOverridePropertySeparation = false;
for (FOptionalPinFromProperty& OverridePropertyEntry : ShowPinForProperties)
{
if (OverridePropertyEntry.PropertyName == OverrideProperty->GetFName())
{
bHadOverridePropertySeparation = true;
break;
}
}
bMadeAfterOverridePinRemoval = false;
UEdGraphPin* Pin = FindPin(Property->GetFName());
if (bHadOverridePropertySeparation)
{
UEdGraphPin* OverridePin = FindPin(OverrideProperty->GetFName());
if (OverridePin)
{
// Override pins are always booleans
check(OverridePin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean);
// If the old override pin's default value was true, then the override should be marked as enabled
PropertyEntry.bIsOverrideEnabled = OverridePin->DefaultValue.ToBool();
// It had an override pin, so conceptually the override pin is visible
PropertyEntry.bIsOverridePinVisible = true;
// Because there was an override pin visible for this property, this property will be forced to have a pin
PropertyEntry.bShowPin = true;
}
else
{
// No override pin, ensure all override bools are false
PropertyEntry.bIsOverrideEnabled = false;
PropertyEntry.bIsOverridePinVisible = false;
}
}
else
{
if (Pin)
{
PropertyEntry.bIsOverrideEnabled = true;
PropertyEntry.bIsOverridePinVisible = true;
}
}
// If the pin for this property, which sets the property's value, does not exist then the user was not trying to set the value
PropertyEntry.bIsSetValuePinVisible = Pin != nullptr;
}
}
}
}
}
}
void UK2Node_MakeStruct::ConvertDeprecatedNode(UEdGraph* Graph, bool bOnlySafeChanges)
{
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
// User may have since deleted the struct type
if (StructType == nullptr)
{
return;
}
// Check to see if the struct has a native make/break that we should try to convert to.
if (StructType->HasMetaData(FBlueprintMetadata::MD_NativeMakeFunction))
{
UFunction* MakeNodeFunction = nullptr;
// If any pins need to change their names during the conversion, add them to the map.
TMap<FName, FName> OldPinToNewPinMap;
if (StructType == TBaseStructure<FRotator>::Get())
{
MakeNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, MakeRotator));
OldPinToNewPinMap.Add(TEXT("Rotator"), UEdGraphSchema_K2::PN_ReturnValue);
}
else if (StructType == TBaseStructure<FVector>::Get())
{
MakeNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED_ThreeParams(UKismetMathLibrary, MakeVector, double, double, double));
OldPinToNewPinMap.Add(TEXT("Vector"), UEdGraphSchema_K2::PN_ReturnValue);
}
else if (StructType == TBaseStructure<FVector2D>::Get())
{
MakeNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED_TwoParams(UKismetMathLibrary, MakeVector2D, double, double));
OldPinToNewPinMap.Add(TEXT("Vector2D"), UEdGraphSchema_K2::PN_ReturnValue);
}
else
{
const FString& MetaData = StructType->GetMetaData(FBlueprintMetadata::MD_NativeMakeFunction);
MakeNodeFunction = FindObject<UFunction>(nullptr, *MetaData, true);
if (MakeNodeFunction)
{
OldPinToNewPinMap.Add(*StructType->GetName(), UEdGraphSchema_K2::PN_ReturnValue);
}
}
if (MakeNodeFunction)
{
Schema->ConvertDeprecatedNodeToFunctionCall(this, MakeNodeFunction, OldPinToNewPinMap, Graph);
}
}
}
#undef LOCTEXT_NAMESPACE