Files
dave jones2 e203cea604 UE-183502 - BP autoconversion adds extraneous conversion nodes (resubmit)
(Resubmit: added check for "multiple self" connections. Even though the types mismatch, we permit these connections.)

The previous attempt to add automatic conversion nodes (24029327) had a flawed design: checking pin connections during rewiring might have an incomplete view of the types of the linked pins.

For example, suppose we have two native, BlueprintCallable functions. One returns a float, and the other accepts a single float. In a Blueprint, we have the output of one linked to the input of the other.

Later, we update both functions to use ints. During rewiring of the function with the return value, it might see that its connection is a float (because that node hasn't been rewired yet). As a result, it'll insert a cast node. Subsequently, when we later rewire the function with the input, it'll see that it's connected to a float, because of the recently inserted cast node. This will add yet another cast node. Since both pin types are the same, there shouldn't be any cast nodes to begin with.

The only safe way to insert cast nodes is after node construction has finished. Instead of handling this during rewiring, we can defer the type checking to early validation. Overall, this simplifies the autoconversion since we just need to iterate over pairs of pins, check for type mismatches, and insert cast nodes where appropriate.

This change effectively reverts CLs 24174262, 24029327, 24218437, and 25444139, and moves the type checking logic to EarlyValidation.

#jira UE-183502
#rb phillip.kavan

[CL 25834276 by dave jones2 in ue5-main branch]
2023-06-06 21:17:28 -04:00

477 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))
{
Result = ERedirectType_Name;
}
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