// Copyright Epic Games, Inc. All Rights Reserved. #include "RigVMModel/RigVMPin.h" #include "RigVMModel/RigVMNode.h" #include "RigVMModel/RigVMGraph.h" #include "RigVMModel/RigVMLink.h" #include "RigVMModel/RigVMController.h" #include "RigVMModel/Nodes/RigVMTemplateNode.h" #include "RigVMCompiler/RigVMCompiler.h" #include "RigVMCore/RigVMExecuteContext.h" #include "RigVMCore/RigVMUnknownType.h" #include "UObject/Package.h" #include "Misc/DefaultValueHelper.h" #include "Misc/PackageName.h" #include "Misc/OutputDevice.h" #include "Misc/StringBuilder.h" #include "Logging/LogScopedVerbosityOverride.h" #include "RigVMModel/Nodes/RigVMCollapseNode.h" #include "RigVMModel/Nodes/RigVMFunctionReferenceNode.h" #include "RigVMModel/Nodes/RigVMFunctionEntryNode.h" #include "RigVMModel/Nodes/RigVMFunctionReturnNode.h" #include "RigVMModel/Nodes/RigVMInvokeEntryNode.h" #if WITH_EDITOR #include "UObject/CoreRedirects.h" #endif URigVMGraph* URigVMInjectionInfo::GetGraph() const { return GetPin()->GetGraph(); } URigVMPin* URigVMInjectionInfo::GetPin() const { return CastChecked(GetOuter()); } URigVMInjectionInfo::FWeakInfo URigVMInjectionInfo::GetWeakInfo() const { FWeakInfo Info; Info.bInjectedAsInput = bInjectedAsInput; Info.Node = Node; Info.InputPinName = InputPin != nullptr ? InputPin->GetFName() : NAME_None; Info.OutputPinName = OutputPin != nullptr ? OutputPin->GetFName() : NAME_None; return Info; } const URigVMPin::FPinOverrideMap URigVMPin::EmptyPinOverrideMap; const URigVMPin::FPinOverride URigVMPin::EmptyPinOverride = URigVMPin::FPinOverride(FRigVMASTProxy(), EmptyPinOverrideMap); const FString URigVMPin::OrphanPinPrefix = TEXT("Orphan::"); bool URigVMPin::SplitPinPathAtStart(const FString& InPinPath, FString& LeftMost, FString& Right) { return InPinPath.Split(TEXT("."), &LeftMost, &Right, ESearchCase::IgnoreCase, ESearchDir::FromStart); } bool URigVMPin::SplitPinPathAtEnd(const FString& InPinPath, FString& Left, FString& RightMost) { return InPinPath.Split(TEXT("."), &Left, &RightMost, ESearchCase::IgnoreCase, ESearchDir::FromEnd); } bool URigVMPin::SplitPinPath(const FString& InPinPath, TArray& Parts) { int32 OriginalPartsCount = Parts.Num(); FString PinPathRemaining = InPinPath; FString Left, Right; while(SplitPinPathAtStart(PinPathRemaining, Left, Right)) { Parts.Add(Left); Left.Empty(); PinPathRemaining = Right; } if (!Right.IsEmpty()) { Parts.Add(Right); } return Parts.Num() > OriginalPartsCount; } FString URigVMPin::JoinPinPath(const FString& Left, const FString& Right) { ensure(!Left.IsEmpty() && !Right.IsEmpty()); return Left + TEXT(".") + Right; } FString URigVMPin::JoinPinPath(const TArray& InParts) { if (InParts.Num() == 0) { return FString(); } FString Result = InParts[0]; for (int32 PartIndex = 1; PartIndex < InParts.Num(); PartIndex++) { Result += TEXT(".") + InParts[PartIndex]; } return Result; } TArray URigVMPin::SplitDefaultValue(const FString& InDefaultValue) { TArray Parts; if (InDefaultValue.IsEmpty()) { return Parts; } ensure(InDefaultValue[0] == TCHAR('(')); ensure(InDefaultValue[InDefaultValue.Len() - 1] == TCHAR(')')); FString Content = InDefaultValue.Mid(1, InDefaultValue.Len() - 2); int32 BraceCount = 0; int32 QuoteCount = 0; int32 LastPartStartIndex = 0; for (int32 CharIndex = 0; CharIndex < Content.Len(); CharIndex++) { TCHAR Char = Content[CharIndex]; if (QuoteCount > 0) { if (Char == TCHAR('"')) { QuoteCount = 0; } } else if (Char == TCHAR('"')) { QuoteCount = 1; } if (Char == TCHAR('(')) { if (QuoteCount == 0) { BraceCount++; } } else if (Char == TCHAR(')')) { if (QuoteCount == 0) { BraceCount--; BraceCount = FMath::Max(BraceCount, 0); } } else if (Char == TCHAR(',') && BraceCount == 0 && QuoteCount == 0) { // ignore whitespaces Parts.Add(Content.Mid(LastPartStartIndex, CharIndex - LastPartStartIndex).Replace(TEXT(" "), TEXT(""))); LastPartStartIndex = CharIndex + 1; } } if (!Content.IsEmpty()) { // ignore whitespaces from the start and end of the string Parts.Add(Content.Mid(LastPartStartIndex).TrimStartAndEnd()); } return Parts; } FString URigVMPin::GetDefaultValueForArray(TConstArrayView DefaultValues) { TStringBuilder<256> Builder; Builder << TCHAR('('); if (DefaultValues.Num()) { Builder << DefaultValues[0]; for (const FString& DefaultValue : DefaultValues.Slice(1, DefaultValues.Num() - 1)) { Builder << TCHAR(',') << DefaultValue; } } Builder << TCHAR(')'); return FString(Builder); } URigVMPin::URigVMPin() : Direction(ERigVMPinDirection::Invalid) , bIsExpanded(false) , bIsConstant(false) , bRequiresWatch(false) , bIsDynamicArray(false) , CPPType(FString()) , CPPTypeObject(nullptr) , CPPTypeObjectPath(NAME_None) , DefaultValue(FString()) , BoundVariablePath_DEPRECATED() { #if UE_BUILD_DEBUG CachedPinPath = GetPinPath(); #endif } bool URigVMPin::NameEquals(const FString& InName, bool bFollowCoreRedirectors) const { if(InName.Equals(GetName())) { return true; } #if WITH_EDITOR if(bFollowCoreRedirectors) { UScriptStruct* Struct = nullptr; if(const URigVMPin* ParentPin = GetParentPin()) { Struct = ParentPin->GetScriptStruct(); } else if(const URigVMUnitNode* UnitNode = Cast(GetNode())) { Struct = UnitNode->GetScriptStruct(); } if(Struct) { typedef TPair FRedirectPinPair; const FRedirectPinPair Key(Struct->GetFName(), InName); static TMap RedirectedPinNames; if(const FName* RedirectedNamePtr = RedirectedPinNames.Find(Key)) { if(RedirectedNamePtr->IsNone()) { return false; } return NameEquals(*RedirectedNamePtr->ToString(), false); } const FCoreRedirectObjectName OldObjectName(*InName, Struct->GetFName(), *Struct->GetOutermost()->GetPathName()); const FCoreRedirectObjectName NewObjectName = FCoreRedirects::GetRedirectedName(ECoreRedirectFlags::Type_Property, OldObjectName); if (OldObjectName != NewObjectName) { RedirectedPinNames.Add(Key, NewObjectName.ObjectName); const FString RedirectedName = NewObjectName.ObjectName.ToString(); return NameEquals(RedirectedName, false); } RedirectedPinNames.Add(Key, NAME_None); } } #endif return false; } FString URigVMPin::GetPinPath(bool bUseNodePath) const { FString PinPath; URigVMPin* ParentPin = GetParentPin(); if (ParentPin) { PinPath = JoinPinPath(ParentPin->GetPinPath(), GetName()); } else { URigVMNode* Node = GetNode(); if (Node != nullptr) { PinPath = JoinPinPath(Node->GetNodePath(bUseNodePath), GetName()); } } #if UE_BUILD_DEBUG if (!bUseNodePath) { CachedPinPath = PinPath; } #endif return PinPath; } FString URigVMPin::GetSubPinPath(const URigVMPin* InParentPin, bool bIncludeParentPinName) const { if (const URigVMPin* ParentPin = GetParentPin()) { if(ParentPin == InParentPin) { if(bIncludeParentPinName) { return JoinPinPath(ParentPin->GetName(),GetName()); } } else { return JoinPinPath(ParentPin->GetSubPinPath(InParentPin, bIncludeParentPinName), GetName()); } } return GetName(); } FString URigVMPin::GetSegmentPath(bool bIncludeRootPin) const { URigVMPin* ParentPin = GetParentPin(); if (ParentPin) { FString ParentSegmentPath = ParentPin->GetSegmentPath(bIncludeRootPin); if (ParentSegmentPath.IsEmpty()) { return GetName(); } return JoinPinPath(ParentSegmentPath, GetName()); } if(bIncludeRootPin) { return GetName(); } return FString(); } void URigVMPin::GetExposedPinChain(TArray& OutExposedPins) const { TArray VisitedPins = {this}; GetExposedPinChainImpl(OutExposedPins, VisitedPins); } void URigVMPin::GetExposedPinChainImpl(TArray& OutExposedPins, TArray& VisitedPins) const { // Variable nodes do not share the operand with their source link if (GetNode()->IsA() && GetDirection() == ERigVMPinDirection::Input) { OutExposedPins.Add(this); return; } // Find the first pin in the chain (source) for (URigVMLink* Link : GetSourceLinks()) { URigVMPin* SourcePin = Link->GetSourcePin(); check(SourcePin != nullptr); // Stop recursion when cycles are present if (VisitedPins.Contains(SourcePin)) { return; } VisitedPins.Add(SourcePin); // If the source is on an entry node, add the pin and make a recursive call on the collapse node pin if (URigVMFunctionEntryNode* EntryNode = Cast(SourcePin->GetNode())) { URigVMGraph* Graph = EntryNode->GetGraph(); if (URigVMCollapseNode* CollapseNode = Cast(Graph->GetOuter())) { if(URigVMPin* CollapseNodePin = CollapseNode->FindPin(SourcePin->GetName())) { CollapseNodePin->GetExposedPinChainImpl(OutExposedPins, VisitedPins); } } } else if (URigVMFunctionReturnNode* ReturnNode = Cast(SourcePin->GetNode())) { URigVMGraph* Graph = ReturnNode->GetGraph(); if (URigVMCollapseNode* CollapseNode = Cast(Graph->GetOuter())) { if(URigVMPin* CollapseNodePin = CollapseNode->FindPin(SourcePin->GetName())) { CollapseNodePin->GetExposedPinChainImpl(OutExposedPins, VisitedPins); } } } // Variable nodes do not share the operand with their source link else if (SourcePin->GetNode()->IsA()) { continue; } else { SourcePin->GetExposedPinChainImpl(OutExposedPins, VisitedPins); } return; } // Add the pins in the OutExposedPins array in depth-first order TSet FoundPins; TArray ToProcess; ToProcess.Push(this); while (!ToProcess.IsEmpty()) { const URigVMPin* Current = ToProcess.Pop(); if (FoundPins.Contains(Current)) { continue; } FoundPins.Add(Current); OutExposedPins.Add(Current); // Add target pins connected to the current pin for (URigVMLink* Link : Current->GetTargetLinks()) { URigVMPin* TargetPin = Link->GetTargetPin(); // Variable nodes do not share the operand with their source link if (TargetPin->GetNode()->IsA()) { continue; } ToProcess.Push(TargetPin); } // If pin is on a collapse node, add entry pin if (URigVMCollapseNode* CollapseNode = Cast(Current->GetNode())) { URigVMFunctionEntryNode* EntryNode = CollapseNode->GetEntryNode(); URigVMPin* EntryPin = EntryNode->FindPin(Current->GetName()); if (EntryPin) { ToProcess.Push(EntryPin); } } // If pin is on a return node, add parent pin on collapse node else if (URigVMFunctionReturnNode* ReturnNode = Cast(Current->GetNode())) { URigVMGraph* Graph = ReturnNode->GetGraph(); if (URigVMCollapseNode* ParentNode = Cast(Graph->GetOuter())) { URigVMPin* CollapseNodePin = ParentNode->FindPin(Current->GetName()); if(CollapseNodePin) { ToProcess.Push(CollapseNodePin); } } } } } FName URigVMPin::GetDisplayName() const { if (DisplayName == NAME_None) { return GetFName(); } if (InjectionInfos.Num() > 0) { FString ProcessedDisplayName = DisplayName.ToString(); for (URigVMInjectionInfo* Injection : InjectionInfos) { if (URigVMUnitNode* InjectedUnitNode = Cast(Injection->Node)) { if (TSharedPtr DefaultStructScope = InjectedUnitNode->ConstructStructInstance()) { FRigVMStruct* DefaultStruct = (FRigVMStruct*)DefaultStructScope->GetStructMemory(); ProcessedDisplayName = DefaultStruct->ProcessPinLabelForInjection(ProcessedDisplayName); } } } return *ProcessedDisplayName; } return DisplayName; } ERigVMPinDirection URigVMPin::GetDirection() const { return Direction; } bool URigVMPin::IsExpanded() const { return bIsExpanded; } bool URigVMPin::IsDefinedAsConstant() const { if (IsArrayElement()) { return GetParentPin()->IsDefinedAsConstant(); } return bIsConstant; } bool URigVMPin::RequiresWatch(const bool bCheckExposedPinChain) const { if (!bRequiresWatch && bCheckExposedPinChain) { TArray VirtualPins; GetExposedPinChain(VirtualPins); for (const URigVMPin* VirtualPin : VirtualPins) { if (VirtualPin->bRequiresWatch) { return true; } } } return bRequiresWatch; } bool URigVMPin::IsStruct() const { if (IsArray()) { return false; } return GetScriptStruct() != nullptr; } bool URigVMPin::IsStructMember() const { URigVMPin* ParentPin = GetParentPin(); if (ParentPin == nullptr) { return false; } return ParentPin->IsStruct(); } bool URigVMPin::IsUObject() const { return RigVMTypeUtils::IsUObjectType(CPPType); } bool URigVMPin::IsInterface() const { return RigVMTypeUtils::IsInterfaceType(CPPType); } bool URigVMPin::IsArray() const { return RigVMTypeUtils::IsArrayType(CPPType); } bool URigVMPin::IsArrayElement() const { URigVMPin* ParentPin = GetParentPin(); if (ParentPin == nullptr) { return false; } return ParentPin->IsArray(); } bool URigVMPin::IsDynamicArray() const { return bIsDynamicArray; } int32 URigVMPin::GetPinIndex() const { int32 Index = INDEX_NONE; URigVMPin* ParentPin = GetParentPin(); if (ParentPin != nullptr) { ParentPin->GetSubPins().Find((URigVMPin*)this, Index); } else { URigVMNode* Node = GetNode(); if (Node != nullptr) { Node->GetPins().Find((URigVMPin*)this, Index); } } return Index; } int32 URigVMPin::GetAbsolutePinIndex() const { return GetNode()->GetAllPinsRecursively().Find((URigVMPin*)this); } void URigVMPin::SetNameFromIndex() { LowLevelRename(*FString::FormatAsNumber(GetPinIndex())); } void URigVMPin::SetDisplayName(const FName& InDisplayName) { if(InDisplayName == GetFName()) { DisplayName = NAME_None; } else { DisplayName = InDisplayName; } } int32 URigVMPin::GetArraySize() const { return SubPins.Num(); } FString URigVMPin::GetCPPType() const { return RigVMTypeUtils::PostProcessCPPType(CPPType, GetCPPTypeObject()); } FString URigVMPin::GetArrayElementCppType() const { if (!IsArray()) { return FString(); } const FString ResolvedType = GetCPPType(); return RigVMTypeUtils::BaseTypeFromArrayType(ResolvedType); } FRigVMTemplateArgument::FType URigVMPin::GetTemplateArgumentType() const { return FRigVMTemplateArgument::FType(GetCPPType(), GetCPPTypeObject()); } bool URigVMPin::IsStringType() const { const FString ResolvedType = GetCPPType(); return ResolvedType.Equals(TEXT("FString")) || ResolvedType.Equals(TEXT("FName")); } bool URigVMPin::IsExecuteContext() const { if (const UScriptStruct* ScriptStruct = GetScriptStruct()) { if (ScriptStruct->IsChildOf(FRigVMExecuteContext::StaticStruct())) { return true; } } return false; } bool URigVMPin::IsWildCard() const { if (const UScriptStruct* ScriptStruct = GetScriptStruct()) { if (ScriptStruct->IsChildOf(FRigVMUnknownType::StaticStruct())) { return true; } } return false; } bool URigVMPin::ContainsWildCardSubPin() const { for(const URigVMPin* SubPin : SubPins) { if(SubPin->IsWildCard() || SubPin->ContainsWildCardSubPin()) { return true; } } return false; } FString URigVMPin::GetDefaultValue() const { return GetDefaultValue(EmptyPinOverride); } FString URigVMPin::GetDefaultValue(const URigVMPin::FPinOverride& InOverride) const { if (FPinOverrideValue const* OverrideValuePtr = InOverride.Value.Find(InOverride.Key.GetSibling((URigVMPin*)this))) { return OverrideValuePtr->DefaultValue; } if (IsArray()) { if (SubPins.Num() > 0) { TArray ElementDefaultValues; for (URigVMPin* SubPin : SubPins) { FString ElementDefaultValue = SubPin->GetDefaultValue(InOverride); if (SubPin->IsStringType()) { ElementDefaultValue = TEXT("\"") + ElementDefaultValue + TEXT("\""); } // if default value is empty, VM may believe that the array is smaller than # of sub-pins // since VM determines the array size by counting the number of element default values. // see URigVMCompiler::FindOrAddRegister() for how pin default values are put into VM memory ensure(!ElementDefaultValue.IsEmpty()); ElementDefaultValues.Add(ElementDefaultValue); } if (ElementDefaultValues.Num() == 0) { return TEXT("()"); } return FString::Printf(TEXT("(%s)"), *FString::Join(ElementDefaultValues, TEXT(","))); } return DefaultValue.IsEmpty() ? TEXT("()") : DefaultValue; } else if (IsStruct()) { if (SubPins.Num() > 0) { TArray MemberDefaultValues; for (URigVMPin* SubPin : SubPins) { FString MemberDefaultValue = SubPin->GetDefaultValue(InOverride); if (SubPin->IsStringType()) { MemberDefaultValue = TEXT("\"") + MemberDefaultValue + TEXT("\""); } else if (MemberDefaultValue.IsEmpty() || MemberDefaultValue == TEXT("()")) { continue; } MemberDefaultValues.Add(FString::Printf(TEXT("%s=%s"), *SubPin->GetName(), *MemberDefaultValue)); } if (MemberDefaultValues.Num() == 0) { return TEXT("()"); } return FString::Printf(TEXT("(%s)"), *FString::Join(MemberDefaultValues, TEXT(","))); } return DefaultValue.IsEmpty() ? TEXT("()") : DefaultValue; } else if (IsArrayElement() && DefaultValue.IsEmpty()) { // array element cannot have an empty default value because when an array pin is // added as a property to a memory storage class, its default value needs to reflect // the number of array elements in that array pin. // for example: // for an array pin of 1 float, the final default value should be "(0.0)" instead of "()". // This default value is used during URigVMCompiler::FindOrAddRegister(...) // Thus in this block, we have to return something like 0.0 instead of empty string return URigVMController::GetPinInitialDefaultValue(this); } return DefaultValue; } template< typename Type> static FString ClampValue(const FString& InValueString, const FString& InMinValueString, const FString& InMaxValueString) { FString RetValString = InValueString; Type RetVal; TTypeFromString::FromString(RetVal, *RetValString); // Enforce min if(!InMinValueString.IsEmpty()) { checkSlow(InMinValueString.IsNumeric()); Type MinValue; TTypeFromString::FromString(MinValue, *InMinValueString); RetVal = FMath::Max(MinValue, RetVal); } //Enforce max if(!InMaxValueString.IsEmpty()) { checkSlow(InMaxValueString.IsNumeric()); Type MaxValue; TTypeFromString::FromString(MaxValue, *InMaxValueString); RetVal = FMath::Min(MaxValue, RetVal); } RetValString = TTypeToString::ToString(RetVal); return RetValString; } bool URigVMPin::IsValidDefaultValue(const FString& InDefaultValue) const { TArray DefaultValues; if (IsArray()) { if(InDefaultValue.IsEmpty()) { return false; } if(InDefaultValue[0] != TCHAR('(')) { return false; } if(InDefaultValue[InDefaultValue.Len() - 1] != TCHAR(')')) { return false; } DefaultValues = URigVMPin::SplitDefaultValue(InDefaultValue); } else { DefaultValues.Add(InDefaultValue); } FString BaseCPPType = GetCPPType() .Replace(RigVMTypeUtils::TArrayPrefix, TEXT("")) .Replace(RigVMTypeUtils::TObjectPtrPrefix, TEXT("")) .Replace(RigVMTypeUtils::TScriptInterfacePrefix, TEXT("")) .Replace(TEXT(">"), TEXT("")); for (const FString& Value : DefaultValues) { // perform single value validation if (UClass* Class = Cast(GetCPPTypeObject())) { if(Value.IsEmpty()) { return true; } UObject* Object = FindObjectFromCPPTypeObjectPath(Value); if(Object == nullptr) { return false; } if(!Object->GetClass()->IsChildOf(Class)) { return false; } } else if (UScriptStruct* ScriptStruct = Cast(GetCPPTypeObject())) { FRigVMPinDefaultValueImportErrorContext ErrorPipe; TArray TempStructBuffer; TempStructBuffer.AddUninitialized(ScriptStruct->GetStructureSize()); ScriptStruct->InitializeDefaultValue(TempStructBuffer.GetData()); { // force logging to the error pipe for error detection LOG_SCOPE_VERBOSITY_OVERRIDE(LogExec, ELogVerbosity::Verbose); ScriptStruct->ImportText(*Value, TempStructBuffer.GetData(), nullptr, PPF_None, &ErrorPipe, ScriptStruct->GetName()); } ScriptStruct->DestroyStruct(TempStructBuffer.GetData()); if (ErrorPipe.NumErrors > 0) { return false; } } else if (UEnum* EnumType = Cast(GetCPPTypeObject())) { FName EnumName(EnumType->GenerateFullEnumName(*Value)); if (!EnumType->IsValidEnumName(EnumName)) { return false; } else { if (EnumType->HasMetaData(TEXT("Hidden"), EnumType->GetIndexByName(EnumName))) { return false; } } } else if (BaseCPPType == TEXT("float")) { if (!FDefaultValueHelper::IsStringValidFloat(Value)) { return false; } } else if (BaseCPPType == TEXT("double")) { if (!FDefaultValueHelper::IsStringValidFloat(Value)) { return false; } } else if (BaseCPPType == TEXT("int32")) { if (!FDefaultValueHelper::IsStringValidInteger(Value)) { return false; } } else if (BaseCPPType == TEXT("bool")) { if (Value != TEXT("True") && Value != TEXT("False")) { return false; } } else if (BaseCPPType == TEXT("FString")) { // anything is allowed } else if (BaseCPPType == TEXT("FName")) { // anything is allowed } } return true; } FString URigVMPin::ClampDefaultValueFromMetaData(const FString& InDefaultValue) const { FString RetVal = InDefaultValue; if (URigVMUnitNode* UnitNode = Cast(GetNode())) { TArray RetVals; TArray DefaultValues; if (IsArray()) { DefaultValues = URigVMPin::SplitDefaultValue(InDefaultValue); } else { DefaultValues.Add(InDefaultValue); } FString MinValue, MaxValue; if (UScriptStruct* ScriptStruct = UnitNode->GetScriptStruct()) { if (FProperty* Property = ScriptStruct->FindPropertyByName(*GetName())) { MinValue = Property->GetMetaData(TEXT("ClampMin")); MaxValue = Property->GetMetaData(TEXT("ClampMax")); } } FString BaseCPPType = GetCPPType() .Replace(RigVMTypeUtils::TArrayPrefix, TEXT("")) .Replace(RigVMTypeUtils::TObjectPtrPrefix, TEXT("")) .Replace(RigVMTypeUtils::TScriptInterfacePrefix, TEXT("")) .Replace(TEXT(">"), TEXT("")); RetVals.SetNumZeroed(DefaultValues.Num()); for (int32 Index = 0; Index < DefaultValues.Num(); ++Index) { const FString& Value = DefaultValues[Index]; if (!MinValue.IsEmpty() || !MaxValue.IsEmpty()) { // perform single value validation if (BaseCPPType == TEXT("float")) { RetVals[Index] = ClampValue(Value, MinValue, MaxValue); } else if (BaseCPPType == TEXT("double")) { RetVals[Index] = ClampValue(Value, MinValue, MaxValue); } else if (BaseCPPType == TEXT("int32")) { RetVals[Index] = ClampValue(Value, MinValue, MaxValue); } else { RetVals[Index] = Value; } } else { RetVals[Index] = Value; } } if (IsArray()) { RetVal = GetDefaultValueForArray(RetVals); } else { RetVal = RetVals[0]; } } return RetVal; } FName URigVMPin::GetCustomWidgetName() const { if (IsArrayElement()) { return GetParentPin()->GetCustomWidgetName(); } return CustomWidgetName; } FText URigVMPin::GetToolTipText() const { if(URigVMNode* Node = GetNode()) { return Node->GetToolTipTextForPin(this); } return FText(); } UObject* URigVMPin::FindObjectFromCPPTypeObjectPath(const FString& InObjectPath) { if (InObjectPath.IsEmpty()) { return nullptr; } if (InObjectPath == FName(NAME_None).ToString()) { return nullptr; } // we do this to avoid ambiguous searches for // common names such as "transform" or "vector" UPackage* Package = nullptr; FString PackageName; FString CPPTypeObjectName = InObjectPath; if (InObjectPath.Split(TEXT("."), &PackageName, &CPPTypeObjectName)) { Package = FindPackage(nullptr, *PackageName); } if (UObject* ObjectWithinPackage = FindObject(Package, *CPPTypeObjectName)) { return ObjectWithinPackage; } return FindFirstObject(*InObjectPath, EFindFirstObjectOptions::NativeFirst | EFindFirstObjectOptions::EnsureIfAmbiguous); } URigVMVariableNode* URigVMPin::GetBoundVariableNode() const { for (TObjectPtr InjectionInfo : InjectionInfos) { if (URigVMVariableNode* VariableNode = Cast(InjectionInfo->Node)) { return VariableNode; } } return nullptr; } // Returns the variable bound to this pin (or NAME_None) const FString URigVMPin::GetBoundVariablePath() const { return GetBoundVariablePath(EmptyPinOverride); } // Returns the variable bound to this pin (or NAME_None) const FString URigVMPin::GetBoundVariablePath(const URigVMPin::FPinOverride& InOverride) const { if (FPinOverrideValue const* OverrideValuePtr = InOverride.Value.Find(InOverride.Key.GetSibling((URigVMPin*)this))) { return OverrideValuePtr->BoundVariablePath; } for (TObjectPtr InjectionInfo : InjectionInfos) { if (URigVMVariableNode* VariableNode = Cast(InjectionInfo->Node)) { FString SegmentPath = InjectionInfo->OutputPin->GetSegmentPath(false); if (SegmentPath.IsEmpty()) { return VariableNode->GetVariableName().ToString(); } return VariableNode->GetVariableName().ToString() + TEXT(".") + SegmentPath; } } return FString(); } // Returns the variable bound to this pin (or NAME_None) FString URigVMPin::GetBoundVariableName() const { if (URigVMVariableNode* VariableNode = GetBoundVariableNode()) { return VariableNode->GetVariableName().ToString(); } return FString(); } // Returns true if this pin is bound to a variable bool URigVMPin::IsBoundToVariable() const { return IsBoundToVariable(EmptyPinOverride); } // Returns true if this pin is bound to a variable bool URigVMPin::IsBoundToVariable(const URigVMPin::FPinOverride& InOverride) const { return !GetBoundVariablePath(InOverride).IsEmpty(); } bool URigVMPin::IsBoundToExternalVariable() const { FString VariableName = GetBoundVariableName(); if (VariableName.IsEmpty()) { return false; } TArray LocalVariables = GetGraph()->GetLocalVariables(true); for (FRigVMGraphVariableDescription& LocalVariable : LocalVariables) { if (LocalVariable.Name == *VariableName) { return false; } } return true; } bool URigVMPin::IsBoundToLocalVariable() const { FString VariableName = GetBoundVariableName(); if (VariableName.IsEmpty()) { return false; } TArray LocalVariables = GetGraph()->GetLocalVariables(false); for (FRigVMGraphVariableDescription& LocalVariable : LocalVariables) { if (LocalVariable.Name == *VariableName) { return true; } } return false; } bool URigVMPin::IsBoundToInputArgument() const { FString VariableName = GetBoundVariableName(); if (VariableName.IsEmpty()) { return false; } if (URigVMFunctionEntryNode* EntryNode = GetGraph()->GetEntryNode()) { if (EntryNode->FindPin(VariableName)) { return true; } } return false; } bool URigVMPin::CanBeBoundToVariable(const FRigVMExternalVariable& InExternalVariable, const FString& InSegmentPath) const { if (!InExternalVariable.IsValid(true)) { return false; } if (bIsConstant) { return false; } // only allow to bind variables to input pins for now if (Direction == ERigVMPinDirection::Output) { return false; } // check type validity // in the future we need to allow arrays as well if (IsArray() && !InSegmentPath.IsEmpty()) { return false; } if (IsArray() != InExternalVariable.bIsArray) { return false; } FName ExternalCPPType = InExternalVariable.TypeName; UObject* ExternalCPPTypeObject = InExternalVariable.TypeObject; if(!InSegmentPath.IsEmpty()) { check(InExternalVariable.Property); const FProperty* Property = InExternalVariable.Property; const FRigVMPropertyPath PropertyPath(Property, InSegmentPath); Property = PropertyPath.GetTailProperty(); FRigVMExternalVariable::GetTypeFromProperty(Property, ExternalCPPType, ExternalCPPTypeObject); } const FString CPPBaseType = IsArray() ? GetArrayElementCppType() : GetCPPType(); return RigVMTypeUtils::AreCompatible(CPPBaseType, GetCPPTypeObject(), ExternalCPPType.ToString(), ExternalCPPTypeObject); } bool URigVMPin::ShowInDetailsPanelOnly() const { #if WITH_EDITOR if (URigVMUnitNode* UnitNode = Cast(GetNode())) { if (GetParentPin() == nullptr) { if (UScriptStruct* ScriptStruct = UnitNode->GetScriptStruct()) { if (FProperty* Property = ScriptStruct->FindPropertyByName(GetFName())) { if (Property->HasMetaData(FRigVMStruct::DetailsOnlyMetaName)) { return true; } } } } } else if(URigVMInvokeEntryNode* InvokeEntryNode = Cast(GetNode())) { if(GetName() == URigVMInvokeEntryNode::EntryName) { return true; } } #endif return false; } // Returns nullptr external variable matching this description FRigVMExternalVariable URigVMPin::ToExternalVariable() const { FRigVMExternalVariable ExternalVariable; FString VariableName = GetBoundVariableName(); if (VariableName.IsEmpty()) { FString NodeName, PinPath; if (!SplitPinPathAtStart(GetPinPath(), NodeName, VariableName)) { return ExternalVariable; } VariableName = VariableName.Replace(TEXT("."), TEXT("_")); } ExternalVariable = RigVMTypeUtils::ExternalVariableFromCPPType(*VariableName, CPPType, GetCPPTypeObject(), false, false); return ExternalVariable; } bool URigVMPin::IsOrphanPin() const { if(URigVMPin* RootPin = GetRootPin()) { if(RootPin != this) { return RootPin->IsOrphanPin(); } } if(URigVMNode* Node = GetNode()) { return Node->OrphanedPins.Contains(this); } return false; } void URigVMPin::UpdateTypeInformationIfRequired() const { if (CPPTypeObject == nullptr) { if (CPPTypeObjectPath != NAME_None) { URigVMPin* MutableThis = (URigVMPin*)this; MutableThis->CPPTypeObject = FindObjectFromCPPTypeObjectPath(CPPTypeObjectPath.ToString()); } } if (CPPTypeObject) { // refresh the type string URigVMPin* MutableThis = (URigVMPin*)this; MutableThis->CPPType = RigVMTypeUtils::PostProcessCPPType(CPPType, CPPTypeObject); } } UObject* URigVMPin::GetCPPTypeObject() const { UpdateTypeInformationIfRequired(); return CPPTypeObject; } UScriptStruct* URigVMPin::GetScriptStruct() const { return Cast(GetCPPTypeObject()); } UEnum* URigVMPin::GetEnum() const { return Cast(GetCPPTypeObject()); } URigVMPin* URigVMPin::GetParentPin() const { return Cast(GetOuter()); } URigVMPin* URigVMPin::GetRootPin() const { URigVMPin* ParentPin = GetParentPin(); if (ParentPin == nullptr) { return const_cast(this); } return ParentPin->GetRootPin(); } bool URigVMPin::IsRootPin() const { return GetParentPin() == nullptr; } URigVMPin* URigVMPin::GetPinForLink() const { URigVMPin* RootPin = GetRootPin(); if (!RootPin->HasInjectedUnitNodes()) { return const_cast(this); } URigVMPin* PinForLink = ((Direction == ERigVMPinDirection::Input) || (Direction == ERigVMPinDirection::IO)) ? RootPin->InjectionInfos.Last()->InputPin : RootPin->InjectionInfos.Last()->OutputPin; if (RootPin != this) { FString SegmentPath = GetSegmentPath(); return PinForLink->FindSubPin(SegmentPath); } return PinForLink; } URigVMLink* URigVMPin::FindLinkForPin(const URigVMPin* InOtherPin) const { for (URigVMLink* Link : Links) { if ((Link->GetSourcePin() == this && Link->GetTargetPin() == InOtherPin) || (Link->GetSourcePin() == InOtherPin && Link->GetTargetPin() == this)) { return Link; } } return nullptr; } URigVMPin* URigVMPin::GetOriginalPinFromInjectedNode() const { if(GetNode() == nullptr) { return nullptr; } if (URigVMInjectionInfo* Injection = GetNode()->GetInjectionInfo()) { URigVMPin* RootPin = GetRootPin(); URigVMPin* OriginalPin = nullptr; if (Injection->bInjectedAsInput && Injection->InputPin == RootPin && Injection->OutputPin) { TArray LinkedPins = Injection->OutputPin->GetLinkedTargetPins(); if (LinkedPins.Num() == 1) { OriginalPin = LinkedPins[0]->GetOriginalPinFromInjectedNode(); } } else if (!Injection->bInjectedAsInput && Injection->OutputPin == RootPin && Injection->InputPin) { TArray LinkedPins = Injection->InputPin->GetLinkedSourcePins(); if (LinkedPins.Num() == 1) { OriginalPin = LinkedPins[0]->GetOriginalPinFromInjectedNode(); } } if (OriginalPin) { if (this != RootPin) { OriginalPin = OriginalPin->FindSubPin(GetSegmentPath()); } return OriginalPin; } } return const_cast(this); } const TArray& URigVMPin::GetSubPins() const { return SubPins; } URigVMPin* URigVMPin::FindSubPin(const FString& InPinPath) const { FString Left, Right; if (!URigVMPin::SplitPinPathAtStart(InPinPath, Left, Right)) { Left = InPinPath; } for (URigVMPin* Pin : SubPins) { if (Pin->NameEquals(Left, true)) { if (Right.IsEmpty()) { return Pin; } return Pin->FindSubPin(Right); } } return nullptr; } bool URigVMPin::IsLinkedTo(URigVMPin* InPin) const { for (URigVMLink* Link : Links) { if (Link->GetSourcePin() == InPin || Link->GetTargetPin() == InPin) { return true; } } return false; } bool URigVMPin::IsLinked(bool bRecursive) const { if(!GetLinks().IsEmpty()) { return true; } if(bRecursive) { for (const URigVMPin* SubPin : SubPins) { if(SubPin->IsLinked(true)) { return true; } } } return false; } const TArray& URigVMPin::GetLinks() const { return Links; } TArray URigVMPin::GetLinkedSourcePins(bool bRecursive) const { TArray Pins; for (URigVMLink* Link : Links) { if (Link->GetTargetPin() == this) { Pins.AddUnique(Link->GetSourcePin()); } } if (bRecursive) { for (URigVMPin* SubPin : SubPins) { Pins.Append(SubPin->GetLinkedSourcePins(bRecursive)); } } return Pins; } TArray URigVMPin::GetLinkedTargetPins(bool bRecursive) const { TArray Pins; for (URigVMLink* Link : Links) { if (Link->GetSourcePin() == this) { Pins.AddUnique(Link->GetTargetPin()); } } if (bRecursive) { for (URigVMPin* SubPin : SubPins) { Pins.Append(SubPin->GetLinkedTargetPins(bRecursive)); } } return Pins; } TArray URigVMPin::GetSourceLinks(bool bRecursive) const { TArray Results; for (URigVMLink* Link : Links) { if (Link->GetTargetPin() == this) { Results.Add(Link); } } if (bRecursive) { for (URigVMPin* SubPin : SubPins) { Results.Append(SubPin->GetSourceLinks(bRecursive)); } } return Results; } TArray URigVMPin::GetTargetLinks(bool bRecursive) const { TArray Results; for (URigVMLink* Link : Links) { if (Link->GetSourcePin() == this) { Results.Add(Link); } } if (bRecursive) { for (URigVMPin* SubPin : SubPins) { Results.Append(SubPin->GetTargetLinks(bRecursive)); } } return Results; } URigVMNode* URigVMPin::GetNode() const { URigVMPin* ParentPin = GetParentPin(); if (ParentPin) { return ParentPin->GetNode(); } URigVMNode* Node = Cast(GetOuter()); if(Node) { return Node; } return nullptr; } URigVMGraph* URigVMPin::GetGraph() const { URigVMNode* Node = GetNode(); if(Node) { return Node->GetGraph(); } return nullptr; } bool URigVMPin::CanLink(URigVMPin* InSourcePin, URigVMPin* InTargetPin, FString* OutFailureReason, const FRigVMByteCode* InByteCode, ERigVMPinDirection InUserLinkDirection, bool bInAllowWildcard) { if (InSourcePin == nullptr || InTargetPin == nullptr) { if (OutFailureReason) { *OutFailureReason = TEXT("One of the pins is nullptr."); } return false; } if (InSourcePin == InTargetPin) { if (OutFailureReason) { *OutFailureReason = TEXT("Source and target pins are the same."); } return false; } URigVMNode* SourceNode = InSourcePin->GetNode(); URigVMNode* TargetNode = InTargetPin->GetNode(); if (SourceNode == TargetNode) { if (OutFailureReason) { *OutFailureReason = TEXT("Source and target pins are on the same node."); } return false; } if (InSourcePin->GetGraph() != InTargetPin->GetGraph()) { if (OutFailureReason) { *OutFailureReason = TEXT("Source and target pins are in different graphs."); } return false; } if (InSourcePin->Direction != ERigVMPinDirection::Output && InSourcePin->Direction != ERigVMPinDirection::IO) { if (OutFailureReason) { *OutFailureReason = TEXT("Source pin is not an output."); } return false; } if (InTargetPin->Direction != ERigVMPinDirection::Input && InTargetPin->Direction != ERigVMPinDirection::IO) { if (OutFailureReason) { *OutFailureReason = TEXT("Target pin is not an input."); } return false; } if (InTargetPin->IsDefinedAsConstant() && !InSourcePin->IsDefinedAsConstant()) { if (OutFailureReason) { *OutFailureReason = TEXT("Cannot connect non-constants to constants."); } return false; } if (InSourcePin->CPPType != InTargetPin->CPPType) { bool bCPPTypesDiffer = true; static const FString Float = TEXT("float"); static const FString Double = TEXT("double"); if (RigVMTypeUtils::AreCompatible(InSourcePin->CPPType, InSourcePin->CPPTypeObject, InTargetPin->CPPType, InTargetPin->CPPTypeObject)) { bCPPTypesDiffer = false; } if (bInAllowWildcard && (InSourcePin->CPPType == RigVMTypeUtils::GetWildCardCPPType() || InTargetPin->CPPType == RigVMTypeUtils::GetWildCardCPPType())) { bCPPTypesDiffer = false; } if (bCPPTypesDiffer) { { auto TemplateNodeSupportsType = [](URigVMPin* InPin, const FString& InCPPType, FString* OutFailureReason) -> bool { if (URigVMTemplateNode* TemplateNode = Cast(InPin->GetNode())) { if (TemplateNode->SupportsType(InPin, InCPPType)) { if (OutFailureReason) { *OutFailureReason = FString(); } } else { return false; } } return true; }; auto IsPinValidForTypeChange = [](URigVMPin* InPin, bool bIsInput, ERigVMPinDirection InDirection) -> bool { if(InPin->IsWildCard()) { return true; } if(!InPin->GetNode()->IsA() || InDirection == ERigVMPinDirection::Invalid) { return false; } if((bIsInput && (InDirection != ERigVMPinDirection::Input)) || (!bIsInput && (InDirection != ERigVMPinDirection::Output))) { if(InPin->IsRootPin() || (InPin->GetParentPin()->IsRootPin() && InPin->IsArrayElement())) { return true; } } return false; }; if(IsPinValidForTypeChange(InSourcePin, false, InUserLinkDirection)) { return TemplateNodeSupportsType(InSourcePin, InTargetPin->GetCPPType(), OutFailureReason); } else if(IsPinValidForTypeChange(InTargetPin, true, InUserLinkDirection)) { return TemplateNodeSupportsType(InTargetPin, InSourcePin->GetCPPType(), OutFailureReason); } } } if (bCPPTypesDiffer) { if (OutFailureReason) { *OutFailureReason = TEXT("Source and target pin types are not compatible."); } return false; } } if(!SourceNode->AllowsLinksOn(InSourcePin)) { if (OutFailureReason) { *OutFailureReason = TEXT("Node doesn't allow links on this pin."); } return false; } if(!TargetNode->AllowsLinksOn(InTargetPin)) { if (OutFailureReason) { *OutFailureReason = TEXT("Node doesn't allow links on this pin."); } return false; } // only allow to link to specified input / output pins on an injected node if (URigVMInjectionInfo* SourceInjectionInfo = SourceNode->GetInjectionInfo()) { if (SourceInjectionInfo->OutputPin != InSourcePin->GetRootPin()) { if (OutFailureReason) { *OutFailureReason = TEXT("Cannot link to a non-exposed pin on an injected node."); } return false; } } // only allow to link to specified input / output pins on an injected node if (URigVMInjectionInfo* TargetInjectionInfo = TargetNode->GetInjectionInfo()) { if (TargetInjectionInfo->InputPin != InTargetPin->GetRootPin()) { if (OutFailureReason) { *OutFailureReason = TEXT("Cannot link to a non-exposed pin on an injected node."); } return false; } } if (InSourcePin->IsLinkedTo(InTargetPin)) { if (OutFailureReason) { *OutFailureReason = TEXT("Source and target pins are already connected."); } return false; } TArray SourceNodes; SourceNodes.Add(SourceNode); if (InByteCode) { int32 TargetNodeInstructionIndex = InByteCode->GetFirstInstructionIndexForSubject(TargetNode); if (TargetNodeInstructionIndex != INDEX_NONE) { for (int32 SourceNodeIndex = 0; SourceNodeIndex < SourceNodes.Num(); SourceNodeIndex++) { bool bNodeCanLinkAnywhere = SourceNodes[SourceNodeIndex]->IsA() || SourceNodes[SourceNodeIndex]->IsA(); if (!bNodeCanLinkAnywhere) { if (URigVMUnitNode* UnitNode = Cast(SourceNodes[SourceNodeIndex])) { // pure / immutable nodes can be connected to any input in any order. // since a new link is going to change the abstract syntax tree if (!UnitNode->IsMutable()) { bNodeCanLinkAnywhere = true; } } } if (!bNodeCanLinkAnywhere) { const int32 SourceNodeInstructionIndex = InByteCode->GetFirstInstructionIndexForSubject(SourceNodes[SourceNodeIndex]); if (SourceNodeInstructionIndex != INDEX_NONE && SourceNodeInstructionIndex > TargetNodeInstructionIndex) { if (OutFailureReason) { static constexpr TCHAR IncorrectNodeOrderMessage[] = TEXT("Source node %s (%s) and target node %s (%s) are in the incorrect order."); *OutFailureReason = FString::Printf( IncorrectNodeOrderMessage, *SourceNodes[SourceNodeIndex]->GetName(), *SourceNodes[SourceNodeIndex]->GetNodeTitle(), *TargetNode->GetName(), *TargetNode->GetNodeTitle()); } return false; } SourceNodes.Append(SourceNodes[SourceNodeIndex]->GetLinkedSourceNodes()); } } } } return true; } bool URigVMPin::HasInjectedUnitNodes() const { for (URigVMInjectionInfo* Info : InjectionInfos) { if (Info->Node.IsA()) { return true; } } return false; }