// Copyright Epic Games, Inc. All Rights Reserved. #include "KismetCastingUtils.h" #include "BPTerminal.h" #include "BlueprintCompiledStatement.h" #include "Containers/Array.h" #include "Containers/EnumAsByte.h" #include "Containers/Map.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "Kismet/BlueprintTypeConversions.h" #include "KismetCompiledFunctionContext.h" #include "KismetCompilerMisc.h" #include "KismetCompiler.h" #include "Misc/AssertionMacros.h" #include "Misc/Optional.h" #include "Templates/Casts.h" #include "Templates/Tuple.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" namespace UE::KismetCompiler::CastingUtils { FBPTerminal* MakeImplicitCastTerminal(FKismetFunctionContext& Context, UEdGraphPin* Net, UEdGraphNode* SourceNode) { check(Net); FBPTerminal* Result = Context.CreateLocalTerminal(); check(Result); Result->CopyFromPin(Net, Context.NetNameMap->MakeValidName(Net, TEXT("ImplicitCast"))); Result->Source = (SourceNode ? SourceNode : Net->GetOwningNode()); return Result; } void RegisterImplicitCasts(FKismetFunctionContext& Context) { auto AddCastMapping = [&Context](UEdGraphPin* DestinationPin, const FConversion& Conversion) { check(DestinationPin); FBPTerminal* NewTerm = MakeImplicitCastTerminal(Context, DestinationPin); UEdGraphNode* OwningNode = DestinationPin->GetOwningNode(); Context.ImplicitCastMap.Add(DestinationPin, FImplicitCastParams{Conversion, NewTerm, OwningNode}); }; // The current context's NetMap can be a mix of input and output pin types. // We need to check both pin types in order to get adequate coverage for potential cast points. for (const auto& It : Context.NetMap) { UEdGraphPin* CurrentPin = It.Key; check(CurrentPin); bool bIsConnectedOutput = (CurrentPin->Direction == EGPD_Output) && (CurrentPin->LinkedTo.Num() > 0); bool bIsConnectedInput = (CurrentPin->Direction == EGPD_Input) && (CurrentPin->LinkedTo.Num() > 0); if (bIsConnectedOutput) { for (UEdGraphPin* DestinationPin : CurrentPin->LinkedTo) { check(DestinationPin); if (Context.ImplicitCastMap.Contains(DestinationPin)) { continue; } FConversion Conversion = GetFloatingPointConversion(*CurrentPin, *DestinationPin); if (Conversion.Type != FloatingPointCastType::None) { AddCastMapping(DestinationPin, Conversion); } } } else if (bIsConnectedInput) { if (Context.ImplicitCastMap.Contains(CurrentPin)) { continue; } if (CurrentPin->LinkedTo.Num() > 0) { const UEdGraphPin* SourcePin = CurrentPin->LinkedTo[0]; check(SourcePin); FConversion Conversion = GetFloatingPointConversion(*SourcePin, *CurrentPin); if (Conversion.Type != FloatingPointCastType::None) { AddCastMapping(CurrentPin, Conversion); } } } } } void InsertImplicitCastStatement(FKismetFunctionContext& Context, const FImplicitCastParams& CastParams, FBPTerminal* RHSTerm) { check(RHSTerm); check(CastParams.TargetTerminal); check(CastParams.TargetNode); EKismetCompiledStatementType CompiledStatementType = {}; UFunction* FunctionToCall = nullptr; switch (CastParams.Conversion.Type) { case FloatingPointCastType::FloatToDouble: CompiledStatementType = KCST_FloatToDoubleCast; break; case FloatingPointCastType::DoubleToFloat: CompiledStatementType = KCST_DoubleToFloatCast; break; case FloatingPointCastType::Container: case FloatingPointCastType::Struct: CompiledStatementType = KCST_CallFunction; FunctionToCall = CastParams.Conversion.Function; check(FunctionToCall); break; default: check(false); break; } FBlueprintCompiledStatement& CastStatement = Context.AppendStatementForNode(CastParams.TargetNode); CastStatement.Type = CompiledStatementType; CastStatement.FunctionToCall = FunctionToCall; CastStatement.LHS = CastParams.TargetTerminal; CastStatement.RHS.Add(RHSTerm); } FBPTerminal* InsertImplicitCastStatement(FKismetFunctionContext& Context, UEdGraphPin* DestinationPin, FBPTerminal* RHSTerm) { check(DestinationPin); check(RHSTerm); FBPTerminal* Result = nullptr; const FImplicitCastParams* CastParams = Context.ImplicitCastMap.Find(DestinationPin); if (CastParams != nullptr) { InsertImplicitCastStatement(Context, *CastParams, RHSTerm); Result = CastParams->TargetTerminal; // Removal of the pin entry indicates to the compiler that the implicit cast has been processed. Context.ImplicitCastMap.Remove(DestinationPin); } return Result; } bool RemoveRegisteredImplicitCast(FKismetFunctionContext& Context, const UEdGraphPin* DestinationPin) { check(DestinationPin); int32 RemovedCount = Context.ImplicitCastMap.Remove(DestinationPin); return (RemovedCount > 0); } FConversion GetFloatingPointConversion(const UEdGraphPin& SourcePin, const UEdGraphPin& DestinationPin) { using namespace UE::Kismet::BlueprintTypeConversions; UClass* BlueprintTypeConversionsClass = UBlueprintTypeConversions::StaticClass(); UFunction* ArrayConversionFunction = BlueprintTypeConversionsClass->FindFunctionByName(TEXT("ConvertArrayType")); check(ArrayConversionFunction); UFunction* SetConversionFunction = BlueprintTypeConversionsClass->FindFunctionByName(TEXT("ConvertSetType")); check(SetConversionFunction); UFunction* MapConversionFunction = BlueprintTypeConversionsClass->FindFunctionByName(TEXT("ConvertMapType")); check(MapConversionFunction); if (SourcePin.PinType.IsMap() && DestinationPin.PinType.IsMap()) { if ((SourcePin.PinType.PinValueType.TerminalCategory == UEdGraphSchema_K2::PC_Real) && (DestinationPin.PinType.PinValueType.TerminalCategory == UEdGraphSchema_K2::PC_Real)) { if ((SourcePin.PinType.PinValueType.TerminalSubCategory == UEdGraphSchema_K2::PC_Float) && ((DestinationPin.PinType.PinValueType.TerminalSubCategory == UEdGraphSchema_K2::PC_Double))) { return {FloatingPointCastType::Container, MapConversionFunction}; } else if ((SourcePin.PinType.PinValueType.TerminalSubCategory == UEdGraphSchema_K2::PC_Double) && ((DestinationPin.PinType.PinValueType.TerminalSubCategory == UEdGraphSchema_K2::PC_Float))) { return {FloatingPointCastType::Container, MapConversionFunction}; } } else if ((SourcePin.PinType.PinValueType.TerminalCategory == UEdGraphSchema_K2::PC_Struct) && (DestinationPin.PinType.PinValueType.TerminalCategory == UEdGraphSchema_K2::PC_Struct)) { UScriptStruct* SourceStruct = Cast(SourcePin.PinType.PinValueType.TerminalSubCategoryObject.Get()); UScriptStruct* DestinationStruct = Cast(DestinationPin.PinType.PinValueType.TerminalSubCategoryObject.Get()); if (FStructConversionTable::Get().GetConversionFunction(SourceStruct, DestinationStruct).IsSet()) { return {FloatingPointCastType::Container, MapConversionFunction}; } } } if ((SourcePin.PinType.PinCategory == UEdGraphSchema_K2::PC_Real) && (DestinationPin.PinType.PinCategory == UEdGraphSchema_K2::PC_Real)) { if ((SourcePin.PinType.PinSubCategory == UEdGraphSchema_K2::PC_Float) && ((DestinationPin.PinType.PinSubCategory == UEdGraphSchema_K2::PC_Double))) { if (SourcePin.PinType.IsArray()) { return {FloatingPointCastType::Container, ArrayConversionFunction}; } else if (SourcePin.PinType.IsSet()) { return {FloatingPointCastType::Container, SetConversionFunction}; } else if (SourcePin.PinType.IsMap()) { return {FloatingPointCastType::Container, MapConversionFunction}; } else { return {FloatingPointCastType::FloatToDouble, nullptr}; } } else if ((SourcePin.PinType.PinSubCategory == UEdGraphSchema_K2::PC_Double) && ((DestinationPin.PinType.PinSubCategory == UEdGraphSchema_K2::PC_Float))) { if (SourcePin.PinType.IsArray()) { return {FloatingPointCastType::Container, ArrayConversionFunction}; } else if (SourcePin.PinType.IsSet()) { return {FloatingPointCastType::Container, SetConversionFunction}; } else if (SourcePin.PinType.IsMap()) { return {FloatingPointCastType::Container, MapConversionFunction}; } else { return {FloatingPointCastType::DoubleToFloat, nullptr}; } } } else if ((SourcePin.PinType.PinCategory == UEdGraphSchema_K2::PC_Struct) && (DestinationPin.PinType.PinCategory == UEdGraphSchema_K2::PC_Struct)) { UScriptStruct* SourceStruct = Cast(SourcePin.PinType.PinSubCategoryObject.Get()); UScriptStruct* DestinationStruct = Cast(DestinationPin.PinType.PinSubCategoryObject.Get()); // Invalid BPs can have missing PinSubCategoryObject values if there was trouble loading the source struct if (SourceStruct && DestinationStruct) { if (TOptional ConversionPair = FStructConversionTable::Get().GetConversionFunction(SourceStruct, DestinationStruct)) { if (SourcePin.PinType.IsArray()) { return { FloatingPointCastType::Container, ArrayConversionFunction }; } else if (SourcePin.PinType.IsSet()) { return { FloatingPointCastType::Container, SetConversionFunction }; } else if (SourcePin.PinType.IsMap()) { return { FloatingPointCastType::Container, MapConversionFunction }; } else { return { FloatingPointCastType::Struct, ConversionPair->Get<1>() }; } } } else { UE_CLOG(!SourceStruct, LogK2Compiler, Warning, TEXT("Source pin '%s' had null struct object (%s)"), *SourcePin.GetName(), *SourcePin.GetOwningNode()->GetFullName()); UE_CLOG(!DestinationStruct, LogK2Compiler, Warning, TEXT("Destination pin '%s' had null struct object (%s)"), *DestinationPin.GetName(), *DestinationPin.GetOwningNode()->GetFullName()); } } return {FloatingPointCastType::None, nullptr}; } } // namespace UE::KismetCompiler::CastingUtils