From cbe820bb953ce956e5734cd02650aca1fed62b1a Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:09:14 -0700 Subject: [PATCH 1/3] Bugfixes and refactor for LogWarning/Error/Note --- Source/Flow/Private/FlowAsset.cpp | 96 ++++++++++++++++++------------- Source/Flow/Public/FlowAsset.h | 6 +- 2 files changed, 60 insertions(+), 42 deletions(-) diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 0294a9a0..61e03689 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -1041,8 +1041,8 @@ void UFlowAsset::CancelAndWarnForUnflushedDeferredTriggers() if (TotalDroppedTriggers == 0 && !Triggers.IsEmpty()) { UE_LOG(LogFlow, Warning, TEXT("FlowAsset '%s' is finishing with %d lingering deferred transition scope(s) — dropping them. " - "This is usually unexpected and may indicate a bug or abnormal termination."), - *GetName(), DeferredTransitionScopes.Num()); + "This is usually unexpected and may indicate a bug or abnormal termination."), + *GetName(), DeferredTransitionScopes.Num()); } TotalDroppedTriggers += Triggers.Num(); @@ -1052,18 +1052,21 @@ void UFlowAsset::CancelAndWarnForUnflushedDeferredTriggers() const UFlowNode* ToNode = GetNode(Trigger.NodeGuid); const UFlowNode* FromNode = Trigger.FromPin.NodeGuid.IsValid() ? GetNode(Trigger.FromPin.NodeGuid) : nullptr; + const FString ToNodeName = ToNode ? ToNode->GetName() : TEXT(""); + const FString FromNodeName = FromNode ? FromNode->GetName() : TEXT(""); + UE_LOG(LogFlow, Error, - TEXT(" → Dropped deferred trigger:\n") - TEXT(" To Node: %s (%s)\n") - TEXT(" To Pin: %s\n") - TEXT(" From Node: %s (%s)\n") - TEXT(" From Pin: %s"), - *ToNode->GetName(), - *Trigger.NodeGuid.ToString(), - *Trigger.PinName.ToString(), - *FromNode->GetName(), - *Trigger.FromPin.NodeGuid.ToString(), - *Trigger.FromPin.PinName.ToString() + TEXT(" → Dropped deferred trigger:\n") + TEXT(" To Node: %s (%s)\n") + TEXT(" To Pin: %s\n") + TEXT(" From Node: %s (%s)\n") + TEXT(" From Pin: %s"), + *ToNodeName, + *Trigger.NodeGuid.ToString(), + *Trigger.PinName.ToString(), + *FromNodeName, + *Trigger.FromPin.NodeGuid.ToString(), + *Trigger.FromPin.PinName.ToString() ); } } @@ -1079,10 +1082,22 @@ bool UFlowAsset::HasStartedFlow() const AActor* UFlowAsset::TryFindActorOwner() const { - const UActorComponent* OwnerAsComponent = Cast(GetOwner()); - if (IsValid(OwnerAsComponent)) + UObject* OwnerObject = GetOwner(); + if (!IsValid(OwnerObject)) + { + return nullptr; + } + + // If the owner is already an Actor, return it directly + if (AActor* OwnerAsActor = Cast(OwnerObject)) { - return Cast(OwnerAsComponent->GetOwner()); + return OwnerAsActor; + } + + // If the owner is a Component, return its owning Actor + if (const UActorComponent* OwnerAsComponent = Cast(OwnerObject)) + { + return OwnerAsComponent->GetOwner(); } return nullptr; @@ -1418,37 +1433,22 @@ bool UFlowAsset::IsBoundToWorld_Implementation() const #if WITH_EDITOR void UFlowAsset::LogError(const FString& MessageToLog, const UFlowNodeBase* Node) const { - // this is runtime log which is should be only called on runtime instances of asset - if (TemplateAsset) - { - UE_LOG(LogFlow, Log, TEXT("Attempted to use Runtime Log on asset instance %s"), *MessageToLog); - } - - if (RuntimeLog.Get()) - { - const TSharedRef TokenizedMessage = RuntimeLog.Get()->Error(*MessageToLog, Node); - BroadcastRuntimeMessageAdded(TokenizedMessage); - } + LogRuntimeMessage(EMessageSeverity::Error, MessageToLog, Node); } void UFlowAsset::LogWarning(const FString& MessageToLog, const UFlowNodeBase* Node) const { - // this is runtime log which is should be only called on runtime instances of asset - if (TemplateAsset) - { - UE_LOG(LogFlow, Log, TEXT("Attempted to use Runtime Log on asset instance %s"), *MessageToLog); - } - - if (RuntimeLog.Get()) - { - const TSharedRef TokenizedMessage = RuntimeLog.Get()->Warning(*MessageToLog, Node); - BroadcastRuntimeMessageAdded(TokenizedMessage); - } + LogRuntimeMessage(EMessageSeverity::Warning, MessageToLog, Node); } void UFlowAsset::LogNote(const FString& MessageToLog, const UFlowNodeBase* Node) const { - // this is runtime log which is should be only called on runtime instances of asset + LogRuntimeMessage(EMessageSeverity::Info, MessageToLog, Node); +} + +void UFlowAsset::LogRuntimeMessage(EMessageSeverity::Type Severity, const FString& MessageToLog, const UFlowNodeBase* Node) const +{ + // this is runtime log which should only be called on runtime instances of asset if (TemplateAsset) { UE_LOG(LogFlow, Log, TEXT("Attempted to use Runtime Log on asset instance %s"), *MessageToLog); @@ -1456,8 +1456,22 @@ void UFlowAsset::LogNote(const FString& MessageToLog, const UFlowNodeBase* Node) if (RuntimeLog.Get()) { - const TSharedRef TokenizedMessage = RuntimeLog.Get()->Note(*MessageToLog, Node); + TSharedRef TokenizedMessage; + switch (Severity) + { + case EMessageSeverity::Error: + TokenizedMessage = RuntimeLog.Get()->Error(*MessageToLog, Node); + break; + + case EMessageSeverity::Warning: + TokenizedMessage = RuntimeLog.Get()->Warning(*MessageToLog, Node); + break; + default: + TokenizedMessage = RuntimeLog.Get()->Note(*MessageToLog, Node); + break; + } + BroadcastRuntimeMessageAdded(TokenizedMessage); } } -#endif +#endif \ No newline at end of file diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index dd0d5062..407676fc 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -484,5 +484,9 @@ class FLOW_API UFlowAsset : public UObject void LogError(const FString& MessageToLog, const UFlowNodeBase* Node) const; void LogWarning(const FString& MessageToLog, const UFlowNodeBase* Node) const; void LogNote(const FString& MessageToLog, const UFlowNodeBase* Node) const; + +private: + /* Shared implementation for LogError/LogWarning/LogNote to avoid code duplication. */ + void LogRuntimeMessage(EMessageSeverity::Type Severity, const FString& MessageToLog, const UFlowNodeBase* Node) const; #endif -}; +}; \ No newline at end of file From 8e08d0e631443b96a498cc8315f3834478a0aa28 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:22:48 -0700 Subject: [PATCH 2/3] compile fix --- Source/Flow/Private/FlowAsset.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 61e03689..2ab2173d 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -1456,7 +1456,7 @@ void UFlowAsset::LogRuntimeMessage(EMessageSeverity::Type Severity, const FStrin if (RuntimeLog.Get()) { - TSharedRef TokenizedMessage; + TSharedPtr TokenizedMessage = nullptr; switch (Severity) { case EMessageSeverity::Error: @@ -1466,12 +1466,13 @@ void UFlowAsset::LogRuntimeMessage(EMessageSeverity::Type Severity, const FStrin case EMessageSeverity::Warning: TokenizedMessage = RuntimeLog.Get()->Warning(*MessageToLog, Node); break; + default: TokenizedMessage = RuntimeLog.Get()->Note(*MessageToLog, Node); break; } - BroadcastRuntimeMessageAdded(TokenizedMessage); + BroadcastRuntimeMessageAdded(TokenizedMessage.ToSharedRef()); } } #endif \ No newline at end of file From 0b4e8fd3a811b0bcd7804e5863d2067150d07fcb Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:50:43 -0700 Subject: [PATCH 3/3] Created FFlowPinConnectionPolicy ## Core Type System - FFlowPinConnectionPolicy: Flexible pin connectivity policy framework - FFlowPinTypeMatchPolicy: Per-type matching rules with EFlowPinTypeMatchRules flags - Standard policy presets: VeryRelaxed, Relaxed, Strict, VeryStrict ### Graph Schema & Pin Connectivity - UFlowGraphSchema::ArePinTypesCompatible(): Policy-driven compatibility checking ### Flow Asset & Runtime - FFlowPinConnectionPolicy integration in UFlowAsset (was in UFlowGraphSchema, editor only, and not easily configurable for projects) - UFlowAsset::GetFlowPinConnectionPolicy(): Runtime policy access for instances ### Predicates & Data Validation - Now supports all FFlowDataPinValue types - Generic equality template: TryCheckResolvedValuesEqual() - Validation with operator/type compatibility checks ### Settings & Configuration - FInstancedStruct-based policy selection in UFlowSettings - Per-asset-subclass policy override capability --- Source/Flow/Flow.Build.cs | 1 - .../FlowNodeAddOn_PredicateCompareValues.cpp | 493 +++++++++++------- Source/Flow/Private/FlowAsset.cpp | 29 ++ Source/Flow/Private/FlowSettings.cpp | 29 ++ Source/Flow/Private/Nodes/FlowNodeBase.cpp | 5 + .../Policies/FlowPinConnectionPolicy.cpp | 167 ++++++ .../Policies/FlowPinTypeMatchPolicy.cpp | 5 + .../Private/Types/FlowGameplayTagUtils.cpp | 81 +++ .../Types/FlowPinTypeNamesStandard.cpp | 205 ++------ .../FlowNodeAddOn_PredicateCompareValues.h | 115 +++- ...owNodeAddOn_PredicateRequireGameplayTags.h | 5 +- Source/Flow/Public/FlowAsset.h | 23 + Source/Flow/Public/FlowSettings.h | 10 + .../Public/Policies/FlowPinConnectionPolicy.h | 85 +++ .../FlowPinTypeMatchPolicy.h | 4 +- Source/Flow/Public/Policies/FlowPolicy.h | 19 + .../FlowStandardPinConnectionPolicies.h | 105 ++++ .../Flow/Public/Types/FlowGameplayTagUtils.h | 41 ++ .../Public/Types/FlowPinTypeNamesStandard.h | 16 +- .../Private/Graph/FlowGraphSchema.cpp | 81 +-- .../Private/Graph/FlowGraphSettings.cpp | 6 +- .../FlowEditor/Public/Graph/FlowGraphSchema.h | 19 +- 22 files changed, 1117 insertions(+), 427 deletions(-) create mode 100644 Source/Flow/Private/Policies/FlowPinConnectionPolicy.cpp create mode 100644 Source/Flow/Private/Policies/FlowPinTypeMatchPolicy.cpp create mode 100644 Source/Flow/Private/Types/FlowGameplayTagUtils.cpp create mode 100644 Source/Flow/Public/Policies/FlowPinConnectionPolicy.h rename Source/Flow/Public/{Asset => Policies}/FlowPinTypeMatchPolicy.h (86%) create mode 100644 Source/Flow/Public/Policies/FlowPolicy.h create mode 100644 Source/Flow/Public/Policies/FlowStandardPinConnectionPolicies.h create mode 100644 Source/Flow/Public/Types/FlowGameplayTagUtils.h diff --git a/Source/Flow/Flow.Build.cs b/Source/Flow/Flow.Build.cs index cc47240d..c6847ce4 100644 --- a/Source/Flow/Flow.Build.cs +++ b/Source/Flow/Flow.Build.cs @@ -18,7 +18,6 @@ public Flow(ReadOnlyTargetRules target) : base(target) "CoreUObject", "DeveloperSettings", "Engine", - "GameplayAbilities", // for FGameplayTagRequirements "GameplayTags", "MovieScene", "MovieSceneTracks", diff --git a/Source/Flow/Private/AddOns/FlowNodeAddOn_PredicateCompareValues.cpp b/Source/Flow/Private/AddOns/FlowNodeAddOn_PredicateCompareValues.cpp index 01790d29..f6057e93 100644 --- a/Source/Flow/Private/AddOns/FlowNodeAddOn_PredicateCompareValues.cpp +++ b/Source/Flow/Private/AddOns/FlowNodeAddOn_PredicateCompareValues.cpp @@ -6,6 +6,8 @@ #include "Types/FlowPinTypeNamesStandard.h" #include "Types/FlowPinTypesStandard.h" #include "Types/FlowDataPinValuesStandard.h" +#include "FlowAsset.h" +#include "Policies/FlowPinConnectionPolicy.h" #define LOCTEXT_NAMESPACE "FlowNodeAddOn_PredicateCompareValues" @@ -93,25 +95,27 @@ bool UFlowNodeAddOn_PredicateCompareValues::IsArithmeticOp() const return EFlowPredicateCompareOperatorType_Classifiers::IsArithmeticOperation(OperatorType); } -bool UFlowNodeAddOn_PredicateCompareValues::IsNumericTypeName(const FName& TypeName) +bool UFlowNodeAddOn_PredicateCompareValues::IsNumericTypeName( + const FFlowPinConnectionPolicy& PinConnectionPolicy, + const FName& TypeName) { - return - IsFloatingPointType(TypeName) || - IsIntegerType(TypeName); + return + PinConnectionPolicy.GetAllSupportedIntegerTypes().Contains(TypeName) || + PinConnectionPolicy.GetAllSupportedFloatTypes().Contains(TypeName); } -bool UFlowNodeAddOn_PredicateCompareValues::IsFloatingPointType(const FName& TypeName) +bool UFlowNodeAddOn_PredicateCompareValues::IsFloatingPointType( + const FFlowPinConnectionPolicy& PinConnectionPolicy, + const FName& TypeName) { - return - TypeName == FFlowPinTypeNamesStandard::PinTypeNameFloat || - TypeName == FFlowPinTypeNamesStandard::PinTypeNameDouble; + return PinConnectionPolicy.GetAllSupportedFloatTypes().Contains(TypeName); } -bool UFlowNodeAddOn_PredicateCompareValues::IsIntegerType(const FName& TypeName) +bool UFlowNodeAddOn_PredicateCompareValues::IsIntegerType( + const FFlowPinConnectionPolicy& PinConnectionPolicy, + const FName& TypeName) { - return - TypeName == FFlowPinTypeNamesStandard::PinTypeNameInt || - TypeName == FFlowPinTypeNamesStandard::PinTypeNameInt64; + return PinConnectionPolicy.GetAllSupportedIntegerTypes().Contains(TypeName); } bool UFlowNodeAddOn_PredicateCompareValues::IsTextType(const FName& TypeName) @@ -132,24 +136,56 @@ bool UFlowNodeAddOn_PredicateCompareValues::IsNameLikeType(const FName& TypeName TypeName == FFlowPinTypeNamesStandard::PinTypeNameEnum; } -bool UFlowNodeAddOn_PredicateCompareValues::IsEnumTypeName(const FName& TypeName) +bool UFlowNodeAddOn_PredicateCompareValues::IsAnyStringLikeTypeName( + const FFlowPinConnectionPolicy& PinConnectionPolicy, + const FName& TypeName) { - return TypeName == FFlowPinTypeNamesStandard::PinTypeNameEnum; + // Special-casing NameLike, since the CompareValues predicate counts Enums as Names + return + IsNameLikeType(TypeName) || + PinConnectionPolicy.GetAllSupportedStringLikeTypes().Contains(TypeName); } -bool UFlowNodeAddOn_PredicateCompareValues::IsAnyStringLikeTypeName(const FName& TypeName) +bool UFlowNodeAddOn_PredicateCompareValues::IsGameplayTagLikeTypeName( + const FFlowPinConnectionPolicy& PinConnectionPolicy, + const FName& TypeName) { - return - IsNameLikeType(TypeName) || - IsTextType(TypeName) || - IsStringType(TypeName); + return PinConnectionPolicy.GetAllSupportedGameplayTagTypes().Contains(TypeName); } -bool UFlowNodeAddOn_PredicateCompareValues::IsGameplayTagLikeTypeName(const FName& TypeName) +bool UFlowNodeAddOn_PredicateCompareValues::IsBoolTypeName(const FName& TypeName) { - return - TypeName == FFlowPinTypeNamesStandard::PinTypeNameGameplayTag || - TypeName == FFlowPinTypeNamesStandard::PinTypeNameGameplayTagContainer; + return TypeName == FFlowPinTypeNamesStandard::PinTypeNameBool; +} + +bool UFlowNodeAddOn_PredicateCompareValues::IsVectorTypeName(const FName& TypeName) +{ + return TypeName == FFlowPinTypeNamesStandard::PinTypeNameVector; +} + +bool UFlowNodeAddOn_PredicateCompareValues::IsRotatorTypeName(const FName& TypeName) +{ + return TypeName == FFlowPinTypeNamesStandard::PinTypeNameRotator; +} + +bool UFlowNodeAddOn_PredicateCompareValues::IsTransformTypeName(const FName& TypeName) +{ + return TypeName == FFlowPinTypeNamesStandard::PinTypeNameTransform; +} + +bool UFlowNodeAddOn_PredicateCompareValues::IsObjectTypeName(const FName& TypeName) +{ + return TypeName == FFlowPinTypeNamesStandard::PinTypeNameObject; +} + +bool UFlowNodeAddOn_PredicateCompareValues::IsClassTypeName(const FName& TypeName) +{ + return TypeName == FFlowPinTypeNamesStandard::PinTypeNameClass; +} + +bool UFlowNodeAddOn_PredicateCompareValues::IsInstancedStructTypeName(const FName& TypeName) +{ + return TypeName == FFlowPinTypeNamesStandard::PinTypeNameInstancedStruct; } #if WITH_EDITOR @@ -177,13 +213,105 @@ void UFlowNodeAddOn_PredicateCompareValues::OnPostEditEnsureAllNamedPropertiesPi } } +EDataValidationResult UFlowNodeAddOn_PredicateCompareValues::ValidateNode() +{ + EDataValidationResult Result = Super::ValidateNode(); + + // Validate that both values are configured + if (!LeftValue.IsValid()) + { + LogValidationError(TEXT("LeftValue is not configured (missing name or pin type).")); + Result = EDataValidationResult::Invalid; + } + + if (!RightValue.IsValid()) + { + LogValidationError(TEXT("RightValue is not configured (missing name or pin type).")); + Result = EDataValidationResult::Invalid; + } + + // Remaining checks require both values to be valid + if (!LeftValue.IsValid() || !RightValue.IsValid()) + { + return Result; + } + + const FFlowPinTypeName LeftPinTypeName = LeftValue.DataPinValue.Get().GetPinTypeName(); + const FFlowPinTypeName RightPinTypeName = RightValue.DataPinValue.Get().GetPinTypeName(); + + // Validate pin type names are set + if (LeftPinTypeName.IsNone()) + { + LogValidationError(TEXT("LeftValue has an unknown or unset pin type.")); + Result = EDataValidationResult::Invalid; + } + + if (RightPinTypeName.IsNone()) + { + LogValidationError(TEXT("RightValue has an unknown or unset pin type.")); + Result = EDataValidationResult::Invalid; + } + + if (LeftPinTypeName.IsNone() || RightPinTypeName.IsNone()) + { + return Result; + } + + // Check type compatibility + + const UFlowAsset* FlowAsset = GetFlowAsset(); + check(IsValid(FlowAsset)); + const FFlowPinConnectionPolicy& PinConnectionPolicy = FlowAsset->GetFlowPinConnectionPolicy(); + + const FName LeftTypeName = LeftPinTypeName.Name; + const FName RightTypeName = RightPinTypeName.Name; + + const bool bSameType = (LeftTypeName == RightTypeName); + + if (!bSameType && !AreComparablePinTypes(PinConnectionPolicy, LeftTypeName, RightTypeName)) + { + LogValidationError(FString::Printf( + TEXT("Pin types are not comparable: '%s' vs '%s'."), + *LeftTypeName.ToString(), + *RightTypeName.ToString())); + Result = EDataValidationResult::Invalid; + } + + // Validate arithmetic operators are only used with numeric types + if (IsArithmeticOp() && + !(IsNumericTypeName(PinConnectionPolicy, LeftTypeName) && IsNumericTypeName(PinConnectionPolicy, RightTypeName))) + { + LogValidationError(FString::Printf( + TEXT("Arithmetic operator '%s' is only supported for numeric pin types (Int/Int64/Float/Double). Current types: '%s' vs '%s'."), + *EFlowPredicateCompareOperatorType_Classifiers::GetOperatorSymbolString(OperatorType), + *LeftTypeName.ToString(), + *RightTypeName.ToString())); + Result = EDataValidationResult::Invalid; + } + + // Warn if both sides have the same authored name (potential user confusion) + if (GetAuthoredValueName(LeftValue) == GetAuthoredValueName(RightValue)) + { + LogValidationWarning(FString::Printf( + TEXT("LeftValue and RightValue have the same name '%s'. This may cause confusion with pin disambiguation."), + *GetAuthoredValueName(LeftValue).ToString())); + } + + if (Result == EDataValidationResult::NotValidated) + { + Result = EDataValidationResult::Valid; + } + + return Result; +} + FText UFlowNodeAddOn_PredicateCompareValues::K2_GetNodeTitle_Implementation() const { using namespace EFlowPredicateCompareOperatorType_Classifiers; const bool bIsClassDefault = HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject); - if (!bIsClassDefault && + if (!bIsClassDefault && GetDefault()->bUseAdaptiveNodeTitles) { const FText LeftDisplayName = FText::FromName(GetAuthoredValueName(LeftValue)); @@ -200,37 +328,26 @@ FText UFlowNodeAddOn_PredicateCompareValues::K2_GetNodeTitle_Implementation() co #endif // WITH_EDITOR -bool UFlowNodeAddOn_PredicateCompareValues::AreComparableStandardPinTypes(const FName& LeftPinTypeName, const FName& RightPinTypeName) +bool UFlowNodeAddOn_PredicateCompareValues::AreComparablePinTypes(const FFlowPinConnectionPolicy& PinConnectionPolicy, const FName& LeftPinTypeName, const FName& RightPinTypeName) { - // TODO (gtaylor) We should update this function to respect the authored pin type compatibility settings. - // We can't at this time, because they are known only to the editor flow code (UFlowGraphSchema::ArePinTypesCompatible), - // but we can conceivably move that information to UFlowAsset (or similar) for runtime and editor-time code to use. - - if (LeftPinTypeName == RightPinTypeName) - { - return true; - } + return PinConnectionPolicy.CanConnectPinTypeNames(LeftPinTypeName, RightPinTypeName); +} - // Numeric: allow int/int64/float/double interchange - if (IsNumericTypeName(LeftPinTypeName) && IsNumericTypeName(RightPinTypeName)) - { - return true; - } +bool UFlowNodeAddOn_PredicateCompareValues::CacheTypeNames(FCachedTypeNames& OutCache) const +{ + OutCache.Reset(); - // String-like: allow Name/String/Text/Enum interchange - // (we include Enums as they have FName values for the purposes of comparison) - if (IsAnyStringLikeTypeName(LeftPinTypeName) && IsAnyStringLikeTypeName(RightPinTypeName)) + if (!LeftValue.IsValid() || !RightValue.IsValid()) { - return true; + LogError(TEXT("Compare Values requires both LeftValue and RightValue to be configured.")); + return false; } - // GameplayTag / Container: allow interchange (type templates can upscale tag -> container) - if (IsGameplayTagLikeTypeName(LeftPinTypeName) && IsGameplayTagLikeTypeName(RightPinTypeName)) - { - return true; - } + OutCache.LeftTypeName = LeftValue.DataPinValue.Get().GetPinTypeName().Name; + OutCache.RightTypeName = RightValue.DataPinValue.Get().GetPinTypeName().Name; + OutCache.bIsValid = true; - return false; + return true; } bool UFlowNodeAddOn_PredicateCompareValues::TryCheckGameplayTagsEqual(bool& bOutIsEqual) const @@ -264,102 +381,38 @@ bool UFlowNodeAddOn_PredicateCompareValues::TryCheckGameplayTagsEqual(bool& bOut return true; } -bool UFlowNodeAddOn_PredicateCompareValues::TryCheckTextEqual(bool& bOutIsEqual) const +bool UFlowNodeAddOn_PredicateCompareValues::TryCheckFallbackStringEqual(bool& bOutIsEqual) const { - // Compare both sides as Text; pin type templates should allow Name/String/Enum -> Text conversion. - FText LeftText; - { - const EFlowDataPinResolveResult ResolveResult = - TryResolveDataPinValue(GetDisambiguatedValueName(LeftValue), LeftText, SingleFromArray); + // Fallback path: try to convert both sides to string via their FFlowDataPinValue::TryConvertValuesToString. + // This enables user-added pin types (from other plugins) to participate in equality comparisons + // as long as they implement TryConvertValuesToString on their FFlowDataPinValue subclass. - if (!FlowPinType::IsSuccess(ResolveResult)) - { - LogError(TEXT("Failed to resolve LeftValue as Text.")); - return false; - } - } + const FFlowDataPinValue* LeftDataPinValue = LeftValue.DataPinValue.GetPtr(); + const FFlowDataPinValue* RightDataPinValue = RightValue.DataPinValue.GetPtr(); - FText RightText; + if (!LeftDataPinValue || !RightDataPinValue) { - const EFlowDataPinResolveResult ResolveResult = - TryResolveDataPinValue(GetDisambiguatedValueName(RightValue), RightText, SingleFromArray); - - if (!FlowPinType::IsSuccess(ResolveResult)) - { - LogError(TEXT("Failed to resolve RightValue as Text.")); - return false; - } + return false; } - bOutIsEqual = LeftText.EqualTo(RightText); - return true; -} - -bool UFlowNodeAddOn_PredicateCompareValues::TryCheckStringEqual(bool& bOutIsEqual) const -{ - // Compare both sides as String; templates can handle Name/Text/Enum -> String if allowed. FString LeftString; + if (!LeftDataPinValue->TryConvertValuesToString(LeftString)) { - const EFlowDataPinResolveResult ResolveResult = - TryResolveDataPinValue(GetDisambiguatedValueName(LeftValue), LeftString, SingleFromArray); - - if (!FlowPinType::IsSuccess(ResolveResult)) - { - LogError(TEXT("Failed to resolve LeftValue as String.")); - return false; - } + LogError(TEXT("Failed to convert LeftValue to String for fallback comparison.")); + return false; } FString RightString; + if (!RightDataPinValue->TryConvertValuesToString(RightString)) { - const EFlowDataPinResolveResult ResolveResult = - TryResolveDataPinValue(GetDisambiguatedValueName(RightValue), RightString, SingleFromArray); - - if (!FlowPinType::IsSuccess(ResolveResult)) - { - LogError(TEXT("Failed to resolve RightValue as String.")); - return false; - } + LogError(TEXT("Failed to convert RightValue to String for fallback comparison.")); + return false; } bOutIsEqual = (LeftString == RightString); return true; } -bool UFlowNodeAddOn_PredicateCompareValues::TryCheckNameEqual(bool& bOutIsEqual) const -{ - // Compare case-insensitively if either side is Name-like. - // We resolve both sides as String and compare IgnoreCase, because: - // - FName itself is case-sensitive in operator==, but your requirement is case-insensitive for Name-like. - // - FlowPinType templates can source Enum values (FName) into string as needed. - FString LeftString; - { - const EFlowDataPinResolveResult ResolveResult = - TryResolveDataPinValue(GetDisambiguatedValueName(LeftValue), LeftString, SingleFromArray); - - if (!FlowPinType::IsSuccess(ResolveResult)) - { - LogError(TEXT("Failed to resolve LeftValue for Name-like comparison.")); - return false; - } - } - - FString RightString; - { - const EFlowDataPinResolveResult ResolveResult = - TryResolveDataPinValue(GetDisambiguatedValueName(RightValue), RightString, SingleFromArray); - - if (!FlowPinType::IsSuccess(ResolveResult)) - { - LogError(TEXT("Failed to resolve RightValue for Name-like comparison.")); - return false; - } - } - - bOutIsEqual = LeftString.Equals(RightString, ESearchCase::IgnoreCase); - return true; -} - bool UFlowNodeAddOn_PredicateCompareValues::CompareDoubleUsingOperator(double LeftValueAsDouble, double RightValueAsDouble) const { FLOW_ASSERT_ENUM_MAX(EFlowPredicateCompareOperatorType, 6); @@ -490,19 +543,44 @@ bool UFlowNodeAddOn_PredicateCompareValues::TryCompareAsInt64() const return CompareInt64UsingOperator(LeftInt64, RightInt64); } +bool UFlowNodeAddOn_PredicateCompareValues::EvaluateEqualityBlock(const TCHAR* TypeLabel, const TFunctionRef CompareFunc) const +{ + if (!IsEqualityOp()) + { + LogError(FString::Printf(TEXT("Arithmetic operators are not supported for %s comparisons."), TypeLabel)); + return false; + } + + bool bIsEqual = false; + if (!CompareFunc(bIsEqual)) + { + return false; + } + + return (OperatorType == EFlowPredicateCompareOperatorType::Equal) == bIsEqual; +} + bool UFlowNodeAddOn_PredicateCompareValues::EvaluatePredicate_Implementation() const { - // All failures are errors and return false. - if (!LeftValue.IsValid() || !RightValue.IsValid()) + // Cache type names once to avoid repeated TInstancedStruct::Get() virtual dispatch. + FCachedTypeNames Cache; + if (!CacheTypeNames(Cache)) { - LogError(TEXT("Compare Values requires both LeftValue and RightValue to be configured.")); return false; } - const FName LeftTypeName = LeftValue.DataPinValue.Get().GetPinTypeName().Name; - const FName RightTypeName = RightValue.DataPinValue.Get().GetPinTypeName().Name; + const UFlowAsset* FlowAsset = GetFlowAsset(); + check(IsValid(FlowAsset)); + const FFlowPinConnectionPolicy& PinConnectionPolicy = FlowAsset->GetFlowPinConnectionPolicy(); + + const FName& LeftTypeName = Cache.LeftTypeName; + const FName& RightTypeName = Cache.RightTypeName; + + const bool bSameType = (LeftTypeName == RightTypeName); - if (!AreComparableStandardPinTypes(LeftTypeName, RightTypeName)) + // Type compatibility gate. + // Same-type unknowns are allowed through for the fallback path at the bottom. + if (!bSameType && !AreComparablePinTypes(PinConnectionPolicy, LeftTypeName, RightTypeName)) { LogError(FString::Printf( TEXT("Compare Values pin types are not comparable: '%s' vs '%s'."), @@ -512,22 +590,17 @@ bool UFlowNodeAddOn_PredicateCompareValues::EvaluatePredicate_Implementation() c return false; } - // Arithmetic operators: numeric only - if (IsArithmeticOp()) + // Arithmetic operators: numeric only (fast reject before the cascade) + if (IsArithmeticOp() && !(IsNumericTypeName(PinConnectionPolicy, LeftTypeName) && IsNumericTypeName(PinConnectionPolicy, RightTypeName))) { - if (!(IsNumericTypeName(LeftTypeName) && IsNumericTypeName(RightTypeName))) - { - LogError(TEXT("Arithmetic operators are only supported for numeric pin types (Int/Int64/Float/Double).")); - return false; - } + LogError(TEXT("Arithmetic operators are only supported for numeric pin types (Int/Int64/Float/Double).")); + return false; } - // Numeric - if (IsNumericTypeName(LeftTypeName) && IsNumericTypeName(RightTypeName)) + // Numeric (full operator set) + if (IsNumericTypeName(PinConnectionPolicy, LeftTypeName) && IsNumericTypeName(PinConnectionPolicy, RightTypeName)) { - // Prefer Int64 if both are integer types (or can be upscaled to int64 precisely). - // Use Double if either side is floating point. - if (IsFloatingPointType(LeftTypeName) || IsFloatingPointType(RightTypeName)) + if (IsFloatingPointType(PinConnectionPolicy, LeftTypeName) || IsFloatingPointType(PinConnectionPolicy, RightTypeName)) { return TryCompareAsDouble(); } @@ -535,67 +608,111 @@ bool UFlowNodeAddOn_PredicateCompareValues::EvaluatePredicate_Implementation() c return TryCompareAsInt64(); } - // Gameplay tags: compare as container (superset). Equality ops only (as per enum). - if (IsGameplayTagLikeTypeName(LeftTypeName) || IsGameplayTagLikeTypeName(RightTypeName)) + // Gameplay tags: compare as container (superset). Equality ops only. + if (IsGameplayTagLikeTypeName(PinConnectionPolicy, LeftTypeName) || IsGameplayTagLikeTypeName(PinConnectionPolicy, RightTypeName)) { - if (!IsEqualityOp()) + return EvaluateEqualityBlock(TEXT("Gameplay Tag"), + [this](bool& bIsEqual) { return TryCheckGameplayTagsEqual(bIsEqual); }); + } + + // String-like (including enums-as-names). Equality ops only. + if (IsAnyStringLikeTypeName(PinConnectionPolicy, LeftTypeName) || IsAnyStringLikeTypeName(PinConnectionPolicy, RightTypeName)) + { + // Dispatch order is significant: + // 1) Name-like (Name OR Enum) => case-insensitive compare via FString + // 2) Text => FText::EqualTo (culture-aware) + // 3) String => exact FString equality + if (IsNameLikeType(LeftTypeName) || IsNameLikeType(RightTypeName)) { - LogError(TEXT("Arithmetic operators are not supported for Gameplay Tags.")); - return false; + return EvaluateEqualityBlock(TEXT("Name/Enum"), + [this](bool& bIsEqual) + { + return TryCheckResolvedValuesEqual(bIsEqual, TEXT("String (Name-like)"), + [](const FString& L, const FString& R) { return L.Equals(R, ESearchCase::IgnoreCase); }); + }); } - bool bIsEqual = false; - if (!TryCheckGameplayTagsEqual(bIsEqual)) + if (IsTextType(LeftTypeName) || IsTextType(RightTypeName)) { - return false; + return EvaluateEqualityBlock(TEXT("Text"), + [this](bool& bIsEqual) + { + return TryCheckResolvedValuesEqual(bIsEqual, TEXT("Text"), + [](const FText& L, const FText& R) { return L.EqualTo(R); }); + }); } - return (OperatorType == EFlowPredicateCompareOperatorType::Equal) == bIsEqual; + return EvaluateEqualityBlock(TEXT("String"), + [this](bool& bIsEqual) + { + return TryCheckResolvedValuesEqual(bIsEqual, TEXT("String")); + }); } - // String-like (including enums-as-names). Equality ops only. - if (IsAnyStringLikeTypeName(LeftTypeName) || IsAnyStringLikeTypeName(RightTypeName)) + // Bool. Equality ops only. + if (IsBoolTypeName(LeftTypeName) && IsBoolTypeName(RightTypeName)) { - if (!IsEqualityOp()) - { - LogError(TEXT("Arithmetic operators are not supported for Name/Text/String/Enum comparisons.")); - return false; - } + return EvaluateEqualityBlock(TEXT("Bool"), + [this](bool& bIsEqual) { return TryCheckResolvedValuesEqual(bIsEqual, TEXT("Bool")); }); + } + + // Vector. Equality ops only, strict comparison (no tolerance). + if (IsVectorTypeName(LeftTypeName) && IsVectorTypeName(RightTypeName)) + { + return EvaluateEqualityBlock(TEXT("Vector"), + [this](bool& bIsEqual) { return TryCheckResolvedValuesEqual(bIsEqual, TEXT("Vector")); }); + } - // Order is significant: - // 1) Name-like (Name OR Enum) => case-insensitive compare - // 2) Text => FText equality (localized) - // 3) String => FString equality - bool bIsEqual = false; + // Rotator. Equality ops only, strict comparison (no tolerance). + if (IsRotatorTypeName(LeftTypeName) && IsRotatorTypeName(RightTypeName)) + { + return EvaluateEqualityBlock(TEXT("Rotator"), + [this](bool& bIsEqual) { return TryCheckResolvedValuesEqual(bIsEqual, TEXT("Rotator")); }); + } - if (IsNameLikeType(LeftTypeName) || IsNameLikeType(RightTypeName)) - { - if (!TryCheckNameEqual(bIsEqual)) - { - return false; - } - } - else if (IsTextType(LeftTypeName) || IsTextType(RightTypeName)) - { - if (!TryCheckTextEqual(bIsEqual)) - { - return false; - } - } - else - { - if (!TryCheckStringEqual(bIsEqual)) + // Transform. Equality ops only, strict comparison (zero tolerance). + if (IsTransformTypeName(LeftTypeName) && IsTransformTypeName(RightTypeName)) + { + return EvaluateEqualityBlock(TEXT("Transform"), + [this](bool& bIsEqual) { - return false; - } - } + return TryCheckResolvedValuesEqual(bIsEqual, TEXT("Transform"), + [](const FTransform& L, const FTransform& R) { return L.Equals(R, 0.0); }); + }); + } + + // Object. Equality ops only, pointer identity. + if (IsObjectTypeName(LeftTypeName) && IsObjectTypeName(RightTypeName)) + { + return EvaluateEqualityBlock(TEXT("Object"), + [this](bool& bIsEqual) { return TryCheckResolvedValuesEqual(bIsEqual, TEXT("Object")); }); + } + + // Class. Equality ops only, strict class identity (not "is derived from"). + if (IsClassTypeName(LeftTypeName) && IsClassTypeName(RightTypeName)) + { + return EvaluateEqualityBlock(TEXT("Class"), + [this](bool& bIsEqual) { return TryCheckResolvedValuesEqual(bIsEqual, TEXT("Class")); }); + } + + // InstancedStruct. Equality ops only, struct type + data equality. + if (IsInstancedStructTypeName(LeftTypeName) && IsInstancedStructTypeName(RightTypeName)) + { + return EvaluateEqualityBlock(TEXT("InstancedStruct"), + [this](bool& bIsEqual) { return TryCheckResolvedValuesEqual(bIsEqual, TEXT("InstancedStruct")); }); + } - return (OperatorType == EFlowPredicateCompareOperatorType::Equal) == bIsEqual; + // Fallback: same-type comparison via string conversion. + // This supports user-added types from other plugins as long as they + // implement TryConvertValuesToString on their FFlowDataPinValue subclass. + if (bSameType) + { + return EvaluateEqualityBlock(*LeftTypeName.ToString(), + [this](bool& bIsEqual) { return TryCheckFallbackStringEqual(bIsEqual); }); } - // TODO (gtaylor) Add Object, Class, InstancedStruct, Vector... etc. support LogError(FString::Printf( - TEXT("Compare Values does not support comparing pin types '%s' and '%s' yet."), + TEXT("Compare Values does not support comparing pin types '%s' and '%s'."), *LeftTypeName.ToString(), *RightTypeName.ToString())); diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 2ab2173d..8d3c3665 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -14,6 +14,7 @@ #include "Nodes/Graph/FlowNode_CustomOutput.h" #include "Nodes/Graph/FlowNode_Start.h" #include "Nodes/Graph/FlowNode_SubGraph.h" +#include "Policies/FlowPinConnectionPolicy.h" #include "Types/FlowAutoDataPinsWorkingData.h" #include "Types/FlowDataPinValue.h" #include "Types/FlowStructUtils.h" @@ -54,6 +55,7 @@ UFlowAsset::UFlowAsset(const FObjectInitializer& ObjectInitializer) , bStartNodePlacedAsGhostNode(false) , TemplateAsset(nullptr) , FinishPolicy(EFlowFinishPolicy::Keep) + , FlowPinConnectionPolicy() { if (!AssetGuid.IsValid()) { @@ -63,6 +65,15 @@ UFlowAsset::UFlowAsset(const FObjectInitializer& ObjectInitializer) ExpectedOwnerClass = GetDefault()->GetDefaultExpectedOwnerClass(); } +void UFlowAsset::PostInitProperties() +{ + Super::PostInitProperties(); + +#if WITH_EDITOR + InitializeFlowPinConnectionPolicy(); +#endif +} + #if WITH_EDITOR void UFlowAsset::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { @@ -1430,7 +1441,25 @@ bool UFlowAsset::IsBoundToWorld_Implementation() const return bWorldBound; } +const FFlowPinConnectionPolicy& UFlowAsset::GetFlowPinConnectionPolicy() const +{ + // Runtime instances delegate to their template, which holds the serialized policy + if (!FlowPinConnectionPolicy.IsValid() && IsValid(TemplateAsset)) + { + return TemplateAsset->GetFlowPinConnectionPolicy(); + } + + check(FlowPinConnectionPolicy.IsValid()); + return FlowPinConnectionPolicy.Get(); +} + #if WITH_EDITOR + +void UFlowAsset::InitializeFlowPinConnectionPolicy() +{ + GetDefault()->GetFlowPinConnectionPolicy(FlowPinConnectionPolicy); +} + void UFlowAsset::LogError(const FString& MessageToLog, const UFlowNodeBase* Node) const { LogRuntimeMessage(EMessageSeverity::Error, MessageToLog, Node); diff --git a/Source/Flow/Private/FlowSettings.cpp b/Source/Flow/Private/FlowSettings.cpp index d51f7a14..8296f904 100644 --- a/Source/Flow/Private/FlowSettings.cpp +++ b/Source/Flow/Private/FlowSettings.cpp @@ -2,11 +2,14 @@ #include "FlowSettings.h" #include "FlowComponent.h" +#include "FlowLogChannels.h" +#include "Policies/FlowStandardPinConnectionPolicies.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowSettings) UFlowSettings::UFlowSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) + , FlowPinConnectionPolicy(FFlowPinConnectionPolicy_VeryRelaxed::StaticStruct()) , bDeferTriggeredOutputsWhileTriggering(true) , bLogOnSignalDisabled(true) , bLogOnSignalPassthrough(true) @@ -27,6 +30,32 @@ void UFlowSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChange (void)OnAdaptiveNodeTitlesChanged.ExecuteIfBound(); } } + +void UFlowSettings::GetFlowPinConnectionPolicy(TInstancedStruct& MutablePinConnectionPolicy) const +{ + if (!ensure(FlowPinConnectionPolicy.IsValid())) + { + return; + } + + const UScriptStruct* BaseStruct = FFlowPinConnectionPolicy::StaticStruct(); + const UScriptStruct* DerivedStruct = FlowPinConnectionPolicy.GetScriptStruct(); + + // Accept FFlowPinConnectionPolicy or any struct derived from it + if (!ensure(IsValid(DerivedStruct)) || !DerivedStruct->IsChildOf(BaseStruct)) + { + UE_LOG(LogFlow, Error, + TEXT("FlowPinConnectionPolicy must derive from %s, but was %s"), + *GetNameSafe(BaseStruct), + *GetNameSafe(DerivedStruct)); + + return; + } + + // Copy the instanced struct payload (preserving the actual derived type) + MutablePinConnectionPolicy.InitializeAsScriptStruct(DerivedStruct, FlowPinConnectionPolicy.GetMemory()); +} + #endif UClass* UFlowSettings::GetDefaultExpectedOwnerClass() const diff --git a/Source/Flow/Private/Nodes/FlowNodeBase.cpp b/Source/Flow/Private/Nodes/FlowNodeBase.cpp index 50ff3ced..043fa4ba 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -990,6 +990,11 @@ EDataValidationResult UFlowNodeBase::ValidateNode() bool UFlowNodeBase::TryAddValueToFormatNamedArguments(const FFlowNamedDataPinProperty& NamedDataPinProperty, FFormatNamedArguments& InOutArguments) const { + if (NamedDataPinProperty.Name.IsNone() || !NamedDataPinProperty.DataPinValue.IsValid()) + { + return false; + } + const FFlowDataPinValue& DataPinValue = NamedDataPinProperty.DataPinValue.Get(); const FFlowPinTypeName PinTypeName = DataPinValue.GetPinTypeName(); diff --git a/Source/Flow/Private/Policies/FlowPinConnectionPolicy.cpp b/Source/Flow/Private/Policies/FlowPinConnectionPolicy.cpp new file mode 100644 index 00000000..02898885 --- /dev/null +++ b/Source/Flow/Private/Policies/FlowPinConnectionPolicy.cpp @@ -0,0 +1,167 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Policies/FlowPinConnectionPolicy.h" +#include "Nodes/FlowPin.h" +#include "Types/FlowPinTypeNamesStandard.h" +#include "FlowAsset.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowPinConnectionPolicy) + +FFlowPinConnectionPolicy::FFlowPinConnectionPolicy() +{ +} + +const FFlowPinTypeMatchPolicy* FFlowPinConnectionPolicy::TryFindPinTypeMatchPolicy(const FName& PinTypeName) const +{ + return PinTypeMatchPolicies.Find(PinTypeName); +} + +bool FFlowPinConnectionPolicy::CanConnectPinTypeNames(const FName& FromOutputPinTypeName, const FName& ToInputPinTypeName) const +{ + const bool bIsInputExecPin = FFlowPin::IsExecPinCategory(ToInputPinTypeName); + const bool bIsOutputExecPin = FFlowPin::IsExecPinCategory(FromOutputPinTypeName); + if (bIsInputExecPin || bIsOutputExecPin) + { + // Exec pins must match exactly (exec ↔ exec only). + return (bIsInputExecPin && bIsOutputExecPin); + } + + const FFlowPinTypeMatchPolicy* FoundPinTypeMatchPolicy = TryFindPinTypeMatchPolicy(ToInputPinTypeName); + if (!FoundPinTypeMatchPolicy) + { + // Could not find PinTypeMatchPolicy for ToInputPinTypeName. + return false; + } + + // PinCategories must match exactly or be in the map of compatible PinCategories for the input pin type + const bool bRequirePinCategoryMatch = + EnumHasAnyFlags(FoundPinTypeMatchPolicy->PinTypeMatchRules, EFlowPinTypeMatchRules::RequirePinCategoryMatch); + + if (bRequirePinCategoryMatch && + FromOutputPinTypeName != ToInputPinTypeName && + !FoundPinTypeMatchPolicy->PinCategories.Contains(FromOutputPinTypeName)) + { + // Pin type mismatch FromOutputPinTypeName != ToInputPinTypeName (and not in compatible categories list). + return false; + } + + return true; +} + +const TSet& FFlowPinConnectionPolicy::GetAllSupportedTypes() const +{ + return FFlowPinTypeNamesStandard::AllStandardTypeNames; +} + +const TSet& FFlowPinConnectionPolicy::GetAllSupportedIntegerTypes() const +{ + return FFlowPinTypeNamesStandard::AllStandardIntegerTypeNames; +} + +const TSet& FFlowPinConnectionPolicy::GetAllSupportedFloatTypes() const +{ + return FFlowPinTypeNamesStandard::AllStandardFloatTypeNames; +} + +const TSet& FFlowPinConnectionPolicy::GetAllSupportedGameplayTagTypes() const +{ + return FFlowPinTypeNamesStandard::AllStandardGameplayTagTypeNames; +} + +const TSet& FFlowPinConnectionPolicy::GetAllSupportedStringLikeTypes() const +{ + return FFlowPinTypeNamesStandard::AllStandardStringLikeTypeNames; +} + +const TSet& FFlowPinConnectionPolicy::GetAllSupportedSubCategoryObjectTypes() const +{ + return FFlowPinTypeNamesStandard::AllStandardSubCategoryObjectTypeNames; +} + +const TSet& FFlowPinConnectionPolicy::GetAllSupportedConvertibleToStringTypes() const +{ + // By default, all types are convertible to string + return GetAllSupportedTypes(); +} + +const TSet& FFlowPinConnectionPolicy::GetAllSupportedReceivingConvertToStringTypes() const +{ + // Only allowing to convert to String type specifically by default. + // Subclasses could choose different or additional type(s) for the ConvertibleToString conversion + static const TSet OnlyStringType = { FFlowPinTypeNamesStandard::PinTypeNameString }; + return OnlyStringType; +} + +EFlowPinTypeMatchRules FFlowPinConnectionPolicy::GetPinTypeMatchRulesForType(const FName& PinTypeName) const +{ + const TSet& SubCategoryObjectTypes = GetAllSupportedSubCategoryObjectTypes(); + if (SubCategoryObjectTypes.Contains(PinTypeName)) + { + return EFlowPinTypeMatchRules::SubCategoryObjectPinTypeMatchRulesMask; + } + else + { + return EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask; + } +} + +#if WITH_EDITOR + +void FFlowPinConnectionPolicy::ConfigurePolicy( + bool bAllowAllTypesConvertibleToString, + bool bAllowAllNumericsConvertible, + bool bAllowAllTypeFamiliesConvertible) +{ + PinTypeMatchPolicies.Reset(); + + const TSet& AllSupportedTypes = GetAllSupportedTypes(); + const TSet& AllGameplayTagTypes= GetAllSupportedGameplayTagTypes(); + const TSet& AllSubCategoryObjectTypes = GetAllSupportedSubCategoryObjectTypes(); + const TSet& AllStringLikeTypes = GetAllSupportedStringLikeTypes(); + const TSet& AllConvertibleToStringTypes = GetAllSupportedConvertibleToStringTypes(); + const TSet& AllReceivingConvertToStringTypes = GetAllSupportedReceivingConvertToStringTypes(); + const TSet& AllIntegerTypes = GetAllSupportedIntegerTypes(); + const TSet& AllFloatTypes = GetAllSupportedFloatTypes(); + TSet AllNumericTypes = AllIntegerTypes; + AllNumericTypes.Append(AllFloatTypes); + + TSet ConnectablePinCategories; + + for (const FName& PinTypeName : AllSupportedTypes) + { + const EFlowPinTypeMatchRules PinTypeMatchRules = GetPinTypeMatchRulesForType(PinTypeName); + + ConnectablePinCategories.Reset(); + + // Add support for AllowAllTypesConvertibleToString + if (bAllowAllTypesConvertibleToString && + AllReceivingConvertToStringTypes.Contains(PinTypeName)) + { + AddConnectablePinTypes(AllConvertibleToStringTypes, PinTypeName, ConnectablePinCategories); + } + + // Add support for numeric type conversion + if (bAllowAllNumericsConvertible) + { + AddConnectablePinTypesIfContains(AllNumericTypes, PinTypeName, ConnectablePinCategories); + } + + if (bAllowAllTypeFamiliesConvertible) + { + // The type families are: Integer, Float, GameplayTag and String-Like + AddConnectablePinTypesIfContains(AllIntegerTypes, PinTypeName, ConnectablePinCategories); + AddConnectablePinTypesIfContains(AllFloatTypes, PinTypeName, ConnectablePinCategories); + AddConnectablePinTypesIfContains(AllGameplayTagTypes, PinTypeName, ConnectablePinCategories); + AddConnectablePinTypesIfContains(AllStringLikeTypes, PinTypeName, ConnectablePinCategories); + } + + // Add the entry for this PinTypeName to the match policies map + PinTypeMatchPolicies.Add( + PinTypeName, + FFlowPinTypeMatchPolicy( + PinTypeMatchRules, + ConnectablePinCategories)); + } +} + +#endif \ No newline at end of file diff --git a/Source/Flow/Private/Policies/FlowPinTypeMatchPolicy.cpp b/Source/Flow/Private/Policies/FlowPinTypeMatchPolicy.cpp new file mode 100644 index 00000000..bd895121 --- /dev/null +++ b/Source/Flow/Private/Policies/FlowPinTypeMatchPolicy.cpp @@ -0,0 +1,5 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Policies/FlowPinTypeMatchPolicy.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowPinTypeMatchPolicy) diff --git a/Source/Flow/Private/Types/FlowGameplayTagUtils.cpp b/Source/Flow/Private/Types/FlowGameplayTagUtils.cpp new file mode 100644 index 00000000..d030d2bd --- /dev/null +++ b/Source/Flow/Private/Types/FlowGameplayTagUtils.cpp @@ -0,0 +1,81 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowGameplayTagUtils.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowGameplayTagUtils) + +bool FFlowGameplayTagRequirements::RequirementsMet(const FGameplayTagContainer& Container) const +{ + const bool bHasRequired = Container.HasAll(RequireTags); + const bool bHasIgnored = Container.HasAny(IgnoreTags); + const bool bMatchQuery = TagQuery.IsEmpty() || TagQuery.Matches(Container); + + return bHasRequired && !bHasIgnored && bMatchQuery; +} + +bool FFlowGameplayTagRequirements::IsEmpty() const +{ + return (RequireTags.Num() == 0 && IgnoreTags.Num() == 0 && TagQuery.IsEmpty()); +} + +FString FFlowGameplayTagRequirements::ToString() const +{ + FString Str; + + if (RequireTags.Num() > 0) + { + Str += FString::Printf(TEXT("require: %s "), *RequireTags.ToStringSimple()); + } + if (IgnoreTags.Num() > 0) + { + Str += FString::Printf(TEXT("ignore: %s "), *IgnoreTags.ToStringSimple()); + } + if (!TagQuery.IsEmpty()) + { + Str += TagQuery.GetDescription(); + } + + return Str; +} + +bool FFlowGameplayTagRequirements::operator==(const FFlowGameplayTagRequirements& Other) const +{ + return RequireTags == Other.RequireTags && IgnoreTags == Other.IgnoreTags && TagQuery == Other.TagQuery; +} + +bool FFlowGameplayTagRequirements::operator!=(const FFlowGameplayTagRequirements& Other) const +{ + return !(*this == Other); +} + +FGameplayTagQuery FFlowGameplayTagRequirements::ConvertTagFieldsToTagQuery() const +{ + const bool bHasRequireTags = !RequireTags.IsEmpty(); + const bool bHasIgnoreTags = !IgnoreTags.IsEmpty(); + + if (!bHasIgnoreTags && !bHasRequireTags) + { + return FGameplayTagQuery{}; + } + + // FGameplayTagContainer::RequirementsMet is HasAll(RequireTags) && !HasAny(IgnoreTags); + FGameplayTagQueryExpression RequiredTagsQueryExpression = FGameplayTagQueryExpression().AllTagsMatch().AddTags(RequireTags); + FGameplayTagQueryExpression IgnoreTagsQueryExpression = FGameplayTagQueryExpression().NoTagsMatch().AddTags(IgnoreTags); + + FGameplayTagQueryExpression RootQueryExpression; + if (bHasRequireTags && bHasIgnoreTags) + { + RootQueryExpression = FGameplayTagQueryExpression().AllExprMatch().AddExpr(RequiredTagsQueryExpression).AddExpr(IgnoreTagsQueryExpression); + } + else if (bHasRequireTags) + { + RootQueryExpression = RequiredTagsQueryExpression; + } + else // bHasIgnoreTags + { + RootQueryExpression = IgnoreTagsQueryExpression; + } + + // Build the expression + return FGameplayTagQuery::BuildQuery(RootQueryExpression); +} diff --git a/Source/Flow/Private/Types/FlowPinTypeNamesStandard.cpp b/Source/Flow/Private/Types/FlowPinTypeNamesStandard.cpp index 28d1b0c9..775b025e 100644 --- a/Source/Flow/Private/Types/FlowPinTypeNamesStandard.cpp +++ b/Source/Flow/Private/Types/FlowPinTypeNamesStandard.cpp @@ -2,161 +2,50 @@ #include "Types/FlowPinTypeNamesStandard.h" -#if WITH_EDITOR - -// Cross-conversion rules: -// - Most* types → String (one-way) (*except InstancedStruct) -// - Numeric: full bidirectional conversion -// - Name/String/Text: full bidirectional -// - GameplayTag ↔ Container: bidirectional - -const TMap FFlowPinTypeNamesStandard::PinTypeMatchPolicies = -{ - { FFlowPinTypeNamesStandard::PinTypeNameBool, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameInt, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { - FFlowPinTypeNamesStandard::PinTypeNameInt64, - FFlowPinTypeNamesStandard::PinTypeNameFloat, - FFlowPinTypeNamesStandard::PinTypeNameDouble - }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameInt64, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { - FFlowPinTypeNamesStandard::PinTypeNameInt, - FFlowPinTypeNamesStandard::PinTypeNameFloat, - FFlowPinTypeNamesStandard::PinTypeNameDouble - }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameFloat, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { - FFlowPinTypeNamesStandard::PinTypeNameInt, - FFlowPinTypeNamesStandard::PinTypeNameInt64, - FFlowPinTypeNamesStandard::PinTypeNameDouble - }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameDouble, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { - FFlowPinTypeNamesStandard::PinTypeNameInt, - FFlowPinTypeNamesStandard::PinTypeNameInt64, - FFlowPinTypeNamesStandard::PinTypeNameFloat - }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameEnum, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameName, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { - FFlowPinTypeNamesStandard::PinTypeNameString, - FFlowPinTypeNamesStandard::PinTypeNameText - }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameString, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { - FFlowPinTypeNamesStandard::PinTypeNameName, - FFlowPinTypeNamesStandard::PinTypeNameText, - - // All other types (except InstancedStruct) can cross-convert to string - FFlowPinTypeNamesStandard::PinTypeNameBool, - FFlowPinTypeNamesStandard::PinTypeNameInt, - FFlowPinTypeNamesStandard::PinTypeNameInt64, - FFlowPinTypeNamesStandard::PinTypeNameFloat, - FFlowPinTypeNamesStandard::PinTypeNameDouble, - FFlowPinTypeNamesStandard::PinTypeNameEnum, - FFlowPinTypeNamesStandard::PinTypeNameVector, - FFlowPinTypeNamesStandard::PinTypeNameRotator, - FFlowPinTypeNamesStandard::PinTypeNameTransform, - FFlowPinTypeNamesStandard::PinTypeNameGameplayTag, - FFlowPinTypeNamesStandard::PinTypeNameGameplayTagContainer, - FFlowPinTypeNamesStandard::PinTypeNameObject, - FFlowPinTypeNamesStandard::PinTypeNameClass - }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameText, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { - FFlowPinTypeNamesStandard::PinTypeNameString, - FFlowPinTypeNamesStandard::PinTypeNameName - }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameVector, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameRotator, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameTransform, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameGameplayTag, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { - FFlowPinTypeNamesStandard::PinTypeNameGameplayTagContainer - }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameGameplayTagContainer, - { - EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, - { - FFlowPinTypeNamesStandard::PinTypeNameGameplayTag - }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameInstancedStruct, - { - EFlowPinTypeMatchRules::SubCategoryObjectPinTypeMatchRulesMask, - { }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameObject, - { - EFlowPinTypeMatchRules::SubCategoryObjectPinTypeMatchRulesMask, - { }, - } - }, - { FFlowPinTypeNamesStandard::PinTypeNameClass, - { - EFlowPinTypeMatchRules::SubCategoryObjectPinTypeMatchRulesMask, - { }, - } - }, -}; -#endif \ No newline at end of file +const TSet FFlowPinTypeNamesStandard::AllStandardTypeNames = + { + PinTypeNameBool, + PinTypeNameInt, + PinTypeNameInt64, + PinTypeNameFloat, + PinTypeNameDouble, + PinTypeNameEnum, + PinTypeNameName, + PinTypeNameString, + PinTypeNameText, + PinTypeNameVector, + PinTypeNameRotator, + PinTypeNameTransform, + PinTypeNameGameplayTag, + PinTypeNameGameplayTagContainer, + PinTypeNameInstancedStruct, + PinTypeNameObject, + PinTypeNameClass, + }; +const TSet FFlowPinTypeNamesStandard::AllStandardIntegerTypeNames = + { + PinTypeNameInt, + PinTypeNameInt64, + }; +const TSet FFlowPinTypeNamesStandard::AllStandardFloatTypeNames = + { + PinTypeNameFloat, + PinTypeNameDouble, + }; +const TSet FFlowPinTypeNamesStandard::AllStandardStringLikeTypeNames = + { + PinTypeNameName, + PinTypeNameString, + PinTypeNameText, + }; +const TSet FFlowPinTypeNamesStandard::AllStandardGameplayTagTypeNames = + { + PinTypeNameGameplayTag, + PinTypeNameGameplayTagContainer, + }; +const TSet FFlowPinTypeNamesStandard::AllStandardSubCategoryObjectTypeNames = + { + PinTypeNameInstancedStruct, + PinTypeNameObject, + PinTypeNameClass, + }; \ No newline at end of file diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateCompareValues.h b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateCompareValues.h index cec8f1ba..3d03e678 100644 --- a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateCompareValues.h +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateCompareValues.h @@ -1,6 +1,8 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #pragma once +#include + #include "AddOns/FlowNodeAddOn.h" #include "Interfaces/FlowPredicateInterface.h" #include "Types/FlowBranchEnums.h" @@ -8,6 +10,8 @@ #include "FlowNodeAddOn_PredicateCompareValues.generated.h" +struct FFlowPinConnectionPolicy; + UCLASS(MinimalApi, NotBlueprintable, meta = (DisplayName = "Compare Values")) class UFlowNodeAddOn_PredicateCompareValues : public UFlowNodeAddOn @@ -28,11 +32,12 @@ class UFlowNodeAddOn_PredicateCompareValues // -- // UFlowNodeBase + virtual EDataValidationResult ValidateNode() override; virtual FText K2_GetNodeTitle_Implementation() const override; // -- /* Utility function for subclasses, if they want to force a named property to be Input or Output. - * Unused in this class. */ + * Unused in this class. */ void OnPostEditEnsureAllNamedPropertiesPinDirection(const FProperty& Property, bool bIsInput); #endif @@ -61,27 +66,48 @@ class UFlowNodeAddOn_PredicateCompareValues bool IsEqualityOp() const; bool IsArithmeticOp() const; - /* Compatibility check by standard pin type names. */ - static bool AreComparableStandardPinTypes(const FName& LeftPinTypeName, const FName& RightPinTypeName); + /* Compatibility check by pin type names. */ + static bool AreComparablePinTypes( + const FFlowPinConnectionPolicy& PinConnectionPolicy, + const FName& LeftPinTypeName, + const FName& RightPinTypeName); // Domain classifiers - static bool IsNumericTypeName(const FName& TypeName); - static bool IsFloatingPointType(const FName& TypeName); - static bool IsIntegerType(const FName& TypeName); + static bool IsNumericTypeName(const FFlowPinConnectionPolicy& PinConnectionPolicy, const FName& TypeName); + static bool IsFloatingPointType(const FFlowPinConnectionPolicy& PinConnectionPolicy, const FName& TypeName); + static bool IsIntegerType(const FFlowPinConnectionPolicy& PinConnectionPolicy, const FName& TypeName); + static bool IsAnyStringLikeTypeName(const FFlowPinConnectionPolicy& PinConnectionPolicy, const FName& TypeName); + static bool IsGameplayTagLikeTypeName(const FFlowPinConnectionPolicy& PinConnectionPolicy, const FName& TypeName); static bool IsTextType(const FName& TypeName); static bool IsStringType(const FName& TypeName); static bool IsNameLikeType(const FName& TypeName); - static bool IsEnumTypeName(const FName& TypeName); - - static bool IsAnyStringLikeTypeName(const FName& TypeName); - static bool IsGameplayTagLikeTypeName(const FName& TypeName); - - // Domain comparisons (these return true if they successfully compared; result is via out param) + static bool IsBoolTypeName(const FName& TypeName); + static bool IsVectorTypeName(const FName& TypeName); + static bool IsRotatorTypeName(const FName& TypeName); + static bool IsTransformTypeName(const FName& TypeName); + static bool IsObjectTypeName(const FName& TypeName); + static bool IsClassTypeName(const FName& TypeName); + static bool IsInstancedStructTypeName(const FName& TypeName); + + // ----------------------------------------------------------------------- + // Domain equality comparisons + // (these return true if they successfully compared; equality result is via out param) + // ----------------------------------------------------------------------- + + /* Generic equality check: resolve both sides as TFlowPinType, compare with Comparator. + * Works for any pin type whose ValueType is supported by the comparator. + * ErrorLabel is used in LogError messages (e.g. "Bool", "Vector", "Object"). + * ComparatorFn defaults to std::equal_to<> (transparent), which uses operator==. */ + template > + bool TryCheckResolvedValuesEqual(bool& bOutIsEqual, const TCHAR* ErrorLabel, ComparatorFn Comparator = {}) const; + + // Domain comparisons that need special handling beyond simple resolve-and-compare bool TryCheckGameplayTagsEqual(bool& bOutIsEqual) const; - bool TryCheckTextEqual(bool& bOutIsEqual) const; - bool TryCheckStringEqual(bool& bOutIsEqual) const; - bool TryCheckNameEqual(bool& bOutIsEqual) const; + + /* Fallback: both sides convert to string via TryConvertValuesToString. + * This supports user-added pin types from other plugins, so long as they implement TryConvertValuesToString. */ + bool TryCheckFallbackStringEqual(bool& bOutIsEqual) const; // Numeric comparisons support full operator set bool TryCompareAsDouble() const; @@ -91,6 +117,11 @@ class UFlowNodeAddOn_PredicateCompareValues bool CompareDoubleUsingOperator(double LeftValueAsDouble, double RightValueAsDouble) const; bool CompareInt64UsingOperator(int64 LeftValueAsInt64, int64 RightValueAsInt64) const; + /* Helper for equality-only type blocks in EvaluatePredicate. + * Guards against arithmetic operators, calls CompareFunc, applies Equal/NotEqual flip. + * Returns the final predicate result, or false on error. */ + bool EvaluateEqualityBlock(const TCHAR* TypeLabel, const TFunctionRef CompareFunc) const; + // These are the DataPinNamedProperty property names // (ie, the name of the property itself, eg "LeftValue") const FName& GetLeftValuePropertyName() const; @@ -100,6 +131,58 @@ class UFlowNodeAddOn_PredicateCompareValues FORCEINLINE const FName& GetAuthoredValueName(const FFlowNamedDataPinProperty& NamedDataPinProperty) const { return NamedDataPinProperty.Name; } /* This is the authored value after being disambiguated (for duplicates). - * Example: how it is presented and indexed on the owning Flow Node. */ + * Example: how it is presented and indexed on the owning Flow Node. */ FORCEINLINE const FName& GetDisambiguatedValueName(const FFlowNamedDataPinProperty& NamedDataPinProperty) const { return NamedDataPinProperty.DataPinValue.Get().PropertyPinName; } + +private: + + /* Cached type names for the current evaluation, to avoid repeated TInstancedStruct::Get() calls. + * Only valid during a single call to EvaluatePredicate_Implementation or ValidateNode. */ + struct FCachedTypeNames + { + FName LeftTypeName; + FName RightTypeName; + bool bIsValid = false; + + void Reset() { bIsValid = false; } + }; + + /* Populate cached type names from the current LeftValue/RightValue. + * Returns false (and logs error) if either value is not configured. */ + bool CacheTypeNames(FCachedTypeNames& OutCache) const; }; + +// ----------------------------------------------------------------------- +// Template implementations +// ----------------------------------------------------------------------- + +template +bool UFlowNodeAddOn_PredicateCompareValues::TryCheckResolvedValuesEqual(bool& bOutIsEqual, const TCHAR* ErrorLabel, ComparatorFn Comparator) const +{ + typename TFlowPinType::ValueType LeftResolved{}; + { + const EFlowDataPinResolveResult ResolveResult = + TryResolveDataPinValue(GetDisambiguatedValueName(LeftValue), LeftResolved, SingleFromArray); + + if (!FlowPinType::IsSuccess(ResolveResult)) + { + LogError(FString::Printf(TEXT("Failed to resolve LeftValue as %s."), ErrorLabel)); + return false; + } + } + + typename TFlowPinType::ValueType RightResolved{}; + { + const EFlowDataPinResolveResult ResolveResult = + TryResolveDataPinValue(GetDisambiguatedValueName(RightValue), RightResolved, SingleFromArray); + + if (!FlowPinType::IsSuccess(ResolveResult)) + { + LogError(FString::Printf(TEXT("Failed to resolve RightValue as %s."), ErrorLabel)); + return false; + } + } + + bOutIsEqual = Comparator(LeftResolved, RightResolved); + return true; +} \ No newline at end of file diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateRequireGameplayTags.h b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateRequireGameplayTags.h index aff9ea73..c38f6c4f 100644 --- a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateRequireGameplayTags.h +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateRequireGameplayTags.h @@ -1,10 +1,9 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #pragma once -#include "GameplayEffectTypes.h" - #include "AddOns/FlowNodeAddOn.h" #include "Interfaces/FlowPredicateInterface.h" +#include "Types/FlowGameplayTagUtils.h" #include "FlowNodeAddOn_PredicateRequireGameplayTags.generated.h" @@ -47,5 +46,5 @@ class UFlowNodeAddOn_PredicateRequireGameplayTags /* Requirements to evaluate the Test Tags with. */ UPROPERTY(EditAnywhere, Category = Configuration) - FGameplayTagRequirements Requirements; + FFlowGameplayTagRequirements Requirements; }; diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index 407676fc..9101c9cb 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -19,6 +19,7 @@ class UFlowNode_CustomOutput; class UFlowNode_CustomInput; class UFlowNode_SubGraph; class UFlowSubsystem; +struct FFlowPinConnectionPolicy; class UEdGraph; class UEdGraphNode; @@ -60,6 +61,9 @@ class FLOW_API UFlowAsset : public UObject ////////////////////////////////////////////////////////////////////////// // Graph (editor-only) +public: + virtual void PostInitProperties() override; + #if WITH_EDITOR public: friend class UFlowGraph; @@ -312,6 +316,7 @@ class FLOW_API UFlowAsset : public UObject UPROPERTY() TArray> RecordedNodes; + UPROPERTY(Transient) EFlowFinishPolicy FinishPolicy; public: @@ -384,6 +389,24 @@ class FLOW_API UFlowAsset : public UObject UFUNCTION(BlueprintPure, Category = "Flow") const TArray& GetRecordedNodes() const { return RecordedNodes; } +////////////////////////////////////////////////////////////////////////// +// FFlowPolicy subclass access + +protected: + /* Policy for UFlowGraphSchema (and others) to use to enforce pin connectivity. + * Also used at runtime by predicates (e.g., CompareValues) for type classification queries. */ + UPROPERTY(VisibleAnywhere, AdvancedDisplay, Category = PinConnection) + TInstancedStruct FlowPinConnectionPolicy; + +#if WITH_EDITOR + /* Override this function to set up a unique policy for a UFlowAsset subclass */ + virtual void InitializeFlowPinConnectionPolicy(); +#endif + +public: + /* FFlowPolicy accessors */ + const FFlowPinConnectionPolicy& GetFlowPinConnectionPolicy() const; + ////////////////////////////////////////////////////////////////////////// // Deferred trigger support diff --git a/Source/Flow/Public/FlowSettings.h b/Source/Flow/Public/FlowSettings.h index 44284bc6..ec102835 100644 --- a/Source/Flow/Public/FlowSettings.h +++ b/Source/Flow/Public/FlowSettings.h @@ -2,10 +2,13 @@ #pragma once #include "Engine/DeveloperSettings.h" +#include "StructUtils/InstancedStruct.h" #include "Templates/SubclassOf.h" #include "UObject/SoftObjectPath.h" #include "FlowSettings.generated.h" +struct FFlowPinConnectionPolicy; + /** * Mostly runtime settings of the Flow Graph. */ @@ -16,8 +19,15 @@ class FLOW_API UFlowSettings : public UDeveloperSettings #if WITH_EDITOR virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + + // Helper to convert FInstancedStruct to TInstancedStruct + void GetFlowPinConnectionPolicy(TInstancedStruct& MutablePinConnectionPolicy) const; #endif + /* The policy for connecting pins in the Flow Graph Editor */ + UPROPERTY(EditAnywhere, config, Category = "Default Policies", DisplayName = "Pin Connection Policy", NoClear, meta = (ExcludeBaseStruct, BaseStruct = "/Script/Flow.FlowPinConnectionPolicy")) + FInstancedStruct FlowPinConnectionPolicy; + /* If True, defer the Triggered Outputs for a FlowAsset while it is currently processing a TriggeredInput. * If False, use legacy behavior for backward compatability. */ UPROPERTY(Config, EditAnywhere, Category = "Flow") diff --git a/Source/Flow/Public/Policies/FlowPinConnectionPolicy.h b/Source/Flow/Public/Policies/FlowPinConnectionPolicy.h new file mode 100644 index 00000000..40701f24 --- /dev/null +++ b/Source/Flow/Public/Policies/FlowPinConnectionPolicy.h @@ -0,0 +1,85 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +#pragma once + +#include "Policies/FlowPolicy.h" +#include "FlowPinTypeMatchPolicy.h" + +#include "FlowPinConnectionPolicy.generated.h" + +// Policy for Flow Pin type relationships. +// +// This struct serves as the domain's type system definition, consumed by: +// 1. The FlowGraphSchema — for pin connection compatibility in the editor +// 2. Runtime predicates (e.g., CompareValues) — for type classification and comparison dispatch +// +// Both consumers access the policy through UFlowAsset::GetFlowPinConnectionPolicy(), +// which allows per-asset-subclass customization of the type system. +USTRUCT() +struct FFlowPinConnectionPolicy : public FFlowPolicy +{ + GENERATED_BODY() + +protected: + /* These are the policies for matching data pin types. */ + UPROPERTY(EditAnywhere, Category = PinConnection, meta = (ShowOnlyInnerProperties)) + TMap PinTypeMatchPolicies; + +public: + FFlowPinConnectionPolicy(); + +////////////////////////////////////////////////////////////////////////// +// Runtime-available queries (used by CompareValues predicate and others) + + FLOW_API const FFlowPinTypeMatchPolicy* TryFindPinTypeMatchPolicy(const FName& PinTypeName) const; + + // Simple connection test using only pin type names + // (more checks will be needed for actual pin connection testing in the Schema) + FLOW_API bool CanConnectPinTypeNames(const FName& FromOutputPinTypeName, const FName& ToInputPinTypeName) const; + + FLOW_API virtual const TSet& GetAllSupportedTypes() const; + FLOW_API virtual const TSet& GetAllSupportedIntegerTypes() const; + FLOW_API virtual const TSet& GetAllSupportedFloatTypes() const; + FLOW_API virtual const TSet& GetAllSupportedGameplayTagTypes() const; + FLOW_API virtual const TSet& GetAllSupportedStringLikeTypes() const; + FLOW_API virtual const TSet& GetAllSupportedSubCategoryObjectTypes() const; + FLOW_API virtual const TSet& GetAllSupportedConvertibleToStringTypes() const; + FLOW_API virtual const TSet& GetAllSupportedReceivingConvertToStringTypes() const; + FLOW_API virtual EFlowPinTypeMatchRules GetPinTypeMatchRulesForType(const FName& PinTypeName) const; + +////////////////////////////////////////////////////////////////////////// +// Policy configuration (editor-only, used to build PinTypeMatchPolicies) + +#if WITH_EDITOR + FLOW_API void ConfigurePolicy( + bool bAllowAllTypesConvertibleToString, + bool bAllowAllNumericsConvertible, + bool bAllowAllTypeFamiliesConvertible); + + FLOW_API FORCEINLINE static void AddConnectablePinTypes(const TSet& PinTypeNames, const FName& PinTypeName, TSet& ConnectablePinCategories); + FLOW_API FORCEINLINE static void AddConnectablePinTypesIfContains(const TSet& PinTypeNames, const FName& PinTypeName, TSet& ConnectablePinCategories); + FLOW_API FORCEINLINE static TSet BuildSetExcludingName(const TSet& NamesSet, const FName& NameToExclude); +#endif +}; + +#if WITH_EDITOR +// Inline implementations +void FFlowPinConnectionPolicy::AddConnectablePinTypes(const TSet& PinTypeNames, const FName& PinTypeName, TSet& ConnectablePinCategories) +{ + ConnectablePinCategories.Append(BuildSetExcludingName(PinTypeNames, PinTypeName)); +} + +void FFlowPinConnectionPolicy::AddConnectablePinTypesIfContains(const TSet& PinTypeNames, const FName& PinTypeName, TSet& ConnectablePinCategories) +{ + if (PinTypeNames.Contains(PinTypeName)) + { + AddConnectablePinTypes(PinTypeNames, PinTypeName, ConnectablePinCategories); + } +} + +TSet FFlowPinConnectionPolicy::BuildSetExcludingName(const TSet& NamesSet, const FName& NameToExclude) +{ + TSet NewSet = NamesSet; + NewSet.Remove(NameToExclude); + return MoveTemp(NewSet); +} +#endif \ No newline at end of file diff --git a/Source/Flow/Public/Asset/FlowPinTypeMatchPolicy.h b/Source/Flow/Public/Policies/FlowPinTypeMatchPolicy.h similarity index 86% rename from Source/Flow/Public/Asset/FlowPinTypeMatchPolicy.h rename to Source/Flow/Public/Policies/FlowPinTypeMatchPolicy.h index 41e2f15b..fc675e54 100644 --- a/Source/Flow/Public/Asset/FlowPinTypeMatchPolicy.h +++ b/Source/Flow/Public/Policies/FlowPinTypeMatchPolicy.h @@ -33,10 +33,10 @@ struct FFlowPinTypeMatchPolicy { GENERATED_BODY() - UPROPERTY() + UPROPERTY(EditAnywhere, Category = PinConnection, meta = (Bitmask, BitmaskEnum = "/Script/Flow.EFlowPinTypeMatchRules")) EFlowPinTypeMatchRules PinTypeMatchRules = EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask; /* Pin categories to allow beyond an exact match. */ - UPROPERTY() + UPROPERTY(EditAnywhere, Category = PinConnection) TSet PinCategories; }; diff --git a/Source/Flow/Public/Policies/FlowPolicy.h b/Source/Flow/Public/Policies/FlowPolicy.h new file mode 100644 index 00000000..e50080af --- /dev/null +++ b/Source/Flow/Public/Policies/FlowPolicy.h @@ -0,0 +1,19 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +#pragma once + +#include "UObject/Class.h" + +#include "FlowPolicy.generated.h" + +// Flow Policy base-class, for policy structs to inherit from +USTRUCT() +struct FFlowPolicy +{ + GENERATED_BODY() + +public: + + virtual ~FFlowPolicy() = default; + + // Nothing of interest here, yet, but defining a class for it, just-in-case +}; diff --git a/Source/Flow/Public/Policies/FlowStandardPinConnectionPolicies.h b/Source/Flow/Public/Policies/FlowStandardPinConnectionPolicies.h new file mode 100644 index 00000000..31efd09f --- /dev/null +++ b/Source/Flow/Public/Policies/FlowStandardPinConnectionPolicies.h @@ -0,0 +1,105 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +#pragma once + +#include "Policies/FlowPinConnectionPolicy.h" + +#include "FlowStandardPinConnectionPolicies.generated.h" + +/* A very relaxed policy that allows maximum data-pin connectivity lenience */ +USTRUCT() +struct FFlowPinConnectionPolicy_VeryRelaxed : public FFlowPinConnectionPolicy +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + FFlowPinConnectionPolicy_VeryRelaxed() + { + // Cross-conversion rules: + // - Most* types → String (one-way) (*except InstancedStruct) + // - Numeric: full bidirectional conversion + // - Name/String/Text: full bidirectional + // - GameplayTag ↔ Container: bidirectional + constexpr bool bAllowAllTypesConvertibleToString = true; + constexpr bool bAllowAllNumericsConvertible = true; + constexpr bool bAllowAllTypeFamiliesConvertible = true; + + ConfigurePolicy( + bAllowAllTypesConvertibleToString, + bAllowAllNumericsConvertible, + bAllowAllTypeFamiliesConvertible); + } +#endif +}; + +/* A moderately relaxed policy that allows reasonable data-pin connectivity lenience */ +USTRUCT() +struct FFlowPinConnectionPolicy_Relaxed : public FFlowPinConnectionPolicy +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + FFlowPinConnectionPolicy_Relaxed() + { + // Cross-conversion rules: + // - Most* types → String (one-way) (*except InstancedStruct) + // - Int/Float/Name/String/Text: full bidirectional + // - GameplayTag ↔ Container: bidirectional + constexpr bool bAllowAllTypesConvertibleToString = true; + constexpr bool bAllowAllNumericsConvertible = false; + constexpr bool bAllowAllTypeFamiliesConvertible = true; + + ConfigurePolicy( + bAllowAllTypesConvertibleToString, + bAllowAllNumericsConvertible, + bAllowAllTypeFamiliesConvertible); + } +#endif +}; + +/* A strict policy that allows no cross-type connectivity (except to string, for dev purposes) */ +USTRUCT() +struct FFlowPinConnectionPolicy_Strict : public FFlowPinConnectionPolicy +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + FFlowPinConnectionPolicy_Strict() + { + // Cross-conversion rules: + // - Most* types → String (one-way) (*except InstancedStruct) + constexpr bool bAllowAllTypesConvertibleToString = true; + constexpr bool bAllowAllNumericsConvertible = false; + constexpr bool bAllowAllTypeFamiliesConvertible = false; + + ConfigurePolicy( + bAllowAllTypesConvertibleToString, + bAllowAllNumericsConvertible, + bAllowAllTypeFamiliesConvertible); + } +#endif +}; + +/* A strict policy that allows no cross-type connectivity at all */ +USTRUCT() +struct FFlowPinConnectionPolicy_VeryStrict : public FFlowPinConnectionPolicy +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + FFlowPinConnectionPolicy_VeryStrict() + { + constexpr bool bAllowAllTypesConvertibleToString = false; + constexpr bool bAllowAllNumericsConvertible = false; + constexpr bool bAllowAllTypeFamiliesConvertible = false; + + ConfigurePolicy( + bAllowAllTypesConvertibleToString, + bAllowAllNumericsConvertible, + bAllowAllTypeFamiliesConvertible); + } +#endif +}; diff --git a/Source/Flow/Public/Types/FlowGameplayTagUtils.h b/Source/Flow/Public/Types/FlowGameplayTagUtils.h new file mode 100644 index 00000000..f16f8f5e --- /dev/null +++ b/Source/Flow/Public/Types/FlowGameplayTagUtils.h @@ -0,0 +1,41 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +#pragma once + +#include "GameplayTagContainer.h" + +#include "FlowGameplayTagUtils.generated.h" + +/** Encapsulate require and ignore tags + * Adapted from FGameplayTagRequirements, but without the GameplayAbilities module dependency */ +USTRUCT(BlueprintType) +struct FFlowGameplayTagRequirements +{ + GENERATED_BODY() + + /** All of these tags must be present */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayTags, meta = (DisplayName = "Must Have Tags")) + FGameplayTagContainer RequireTags; + + /** None of these tags may be present */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayTags, meta = (DisplayName = "Must Not Have Tags")) + FGameplayTagContainer IgnoreTags; + + /** Build up a more complex query that can't be expressed with RequireTags/IgnoreTags alone */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayTags, meta = (DisplayName = "Query Must Match")) + FGameplayTagQuery TagQuery; + + /** True if all required tags and no ignore tags found */ + FLOW_API bool RequirementsMet(const FGameplayTagContainer& Container) const; + + /** True if neither RequireTags or IgnoreTags has any tags */ + FLOW_API bool IsEmpty() const; + + /** Return debug string */ + FLOW_API FString ToString() const; + + FLOW_API bool operator==(const FFlowGameplayTagRequirements& Other) const; + FLOW_API bool operator!=(const FFlowGameplayTagRequirements& Other) const; + + /** Converts the RequireTags and IgnoreTags fields into an equivalent FGameplayTagQuery */ + [[nodiscard]] FLOW_API FGameplayTagQuery ConvertTagFieldsToTagQuery() const; +}; diff --git a/Source/Flow/Public/Types/FlowPinTypeNamesStandard.h b/Source/Flow/Public/Types/FlowPinTypeNamesStandard.h index ecdd41d4..fd163736 100644 --- a/Source/Flow/Public/Types/FlowPinTypeNamesStandard.h +++ b/Source/Flow/Public/Types/FlowPinTypeNamesStandard.h @@ -1,8 +1,8 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #pragma once +#include "Containers/Set.h" #include "UObject/NameTypes.h" -#include "Asset/FlowPinTypeMatchPolicy.h" struct FFlowPinTypeNamesStandard { @@ -29,9 +29,11 @@ struct FFlowPinTypeNamesStandard FLOW_API static constexpr const TCHAR* PinTypeNameObject = TEXT("Object"); FLOW_API static constexpr const TCHAR* PinTypeNameClass = TEXT("Class"); -#if WITH_EDITOR - /* These are the default pin match policies for input pin connections in the UFlowGraphSchema. - * Schema subclasses can modify this map. . */ - FLOW_API static const TMap PinTypeMatchPolicies; -#endif -}; + // Sets of PinTypeNames that will be used in the FFlowPinConnectionPolicy functions + FLOW_API static const TSet AllStandardTypeNames; + FLOW_API static const TSet AllStandardIntegerTypeNames; + FLOW_API static const TSet AllStandardFloatTypeNames; + FLOW_API static const TSet AllStandardStringLikeTypeNames; + FLOW_API static const TSet AllStandardGameplayTagTypeNames; + FLOW_API static const TSet AllStandardSubCategoryObjectTypeNames; +}; \ No newline at end of file diff --git a/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp b/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp index e6e0d3ae..a957a015 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp @@ -22,6 +22,7 @@ #include "Nodes/Graph/FlowNode_CustomInput.h" #include "Nodes/Graph/FlowNode_Start.h" #include "Nodes/Route/FlowNode_Reroute.h" +#include "Policies/FlowPinConnectionPolicy.h" #include "Types/FlowPinType.h" #include "AssetRegistry/AssetRegistryModule.h" @@ -199,19 +200,6 @@ UFlowGraphSchema::UFlowGraphSchema(const FObjectInitializer& ObjectInitializer) } } -void UFlowGraphSchema::EnsurePinTypesInitialized() -{ - if (PinTypeMatchPolicies.IsEmpty()) - { - InitializedPinTypes(); - } -} - -void UFlowGraphSchema::InitializedPinTypes() -{ - PinTypeMatchPolicies = FFlowPinTypeNamesStandard::PinTypeMatchPolicies; -} - void UFlowGraphSchema::SubscribeToAssetChanges() { const FAssetRegistryModule& AssetRegistry = FModuleManager::LoadModuleChecked(AssetRegistryConstants::ModuleName); @@ -318,15 +306,17 @@ bool UFlowGraphSchema::ArePinsCompatible(const UEdGraphPin* PinA, const UEdGraph } } - return ArePinTypesCompatible(OutputPin->PinType, InputPin->PinType, CallingContext, bIgnoreArray); + return ArePinTypesCompatible(*OutputPin, *InputPin, CallingContext, bIgnoreArray); } bool UFlowGraphSchema::ArePinTypesCompatible( - const FEdGraphPinType& OutputPinType, - const FEdGraphPinType& InputPinType, + const UEdGraphPin& OutputPin, + const UEdGraphPin& InputPin, const UClass* CallingContext, bool bIgnoreArray) const { + const FEdGraphPinType& InputPinType = InputPin.PinType; + const FEdGraphPinType& OutputPinType = OutputPin.PinType; const bool bIsInputExecPin = FFlowPin::IsExecPinCategory(InputPinType.PinCategory); const bool bIsOutputExecPin = FFlowPin::IsExecPinCategory(OutputPinType.PinCategory); if (bIsInputExecPin || bIsOutputExecPin) @@ -335,28 +325,24 @@ bool UFlowGraphSchema::ArePinTypesCompatible( return (bIsInputExecPin && bIsOutputExecPin); } - UFlowGraphSchema* MutableThis = const_cast(this); - MutableThis->EnsurePinTypesInitialized(); - - const FFlowPinTypeMatchPolicy* FoundPinTypeMatchPolicy = PinTypeMatchPolicies.Find(InputPinType.PinCategory); - if (!FoundPinTypeMatchPolicy) + const UFlowAsset* FlowAsset = GetFlowAssetForPin(OutputPin); + if (!IsValid(FlowAsset)) { - // Could not find PinTypeMatchPolicy for InputPinType.PinCategory. + UE_LOG(LogFlowEditor, Error, TEXT("Could not find the FlowAsset when trying to check ArePinTypesCompatible!")); return false; } - // PinCategories must match exactly or be in the map of compatible PinCategories for the input pin type - const bool bRequirePinCategoryMatch = - EnumHasAnyFlags(FoundPinTypeMatchPolicy->PinTypeMatchRules, EFlowPinTypeMatchRules::RequirePinCategoryMatch); - - if (bRequirePinCategoryMatch && - OutputPinType.PinCategory != InputPinType.PinCategory && - !FoundPinTypeMatchPolicy->PinCategories.Contains(OutputPinType.PinCategory)) + // Get the PinConnectionPolicy from the FlowAsset + const FFlowPinConnectionPolicy& FlowPinConnectionPolicy = FlowAsset->GetFlowPinConnectionPolicy(); + if (!FlowPinConnectionPolicy.CanConnectPinTypeNames(OutputPinType.PinCategory, InputPinType.PinCategory)) { - // Pin type mismatch OutputPinType.PinCategory != InputPinType.PinCategory (and not in compatible categories list). + // Type-name based check failed return false; } + const FFlowPinTypeMatchPolicy* FoundPinTypeMatchPolicy = FlowPinConnectionPolicy.TryFindPinTypeMatchPolicy(InputPinType.PinCategory); + checkf(FoundPinTypeMatchPolicy, TEXT("Should fail CanConnectPinTypeNames, if no MatchPolicy")); + // RequirePinCategoryMemberReference const bool bRequirePinCategoryMemberReferenceMatch = EnumHasAnyFlags(FoundPinTypeMatchPolicy->PinTypeMatchRules, EFlowPinTypeMatchRules::RequirePinCategoryMemberReferenceMatch); @@ -627,6 +613,29 @@ bool UFlowGraphSchema::IsPIESimulating() return GEditor->bIsSimulatingInEditor || (GEditor->PlayWorld != nullptr); } +const UFlowNodeBase* UFlowGraphSchema::GetFlowNodeBaseForPin(const UEdGraphPin& EdGraphPin) +{ + if (const UFlowGraphNode* OwningFlowGraphNode = CastChecked(EdGraphPin.GetOwningNode(), ECastCheckedType::NullAllowed)) + { + return OwningFlowGraphNode->GetFlowNodeBase(); + } + + return nullptr; +} + +const UFlowAsset* UFlowGraphSchema::GetFlowAssetForPin(const UEdGraphPin& EdGraphPin) +{ + if (const UEdGraphNode* OwningEdGraphNode = EdGraphPin.GetOwningNode()) + { + if (const UFlowGraph* FlowGraph = CastChecked(OwningEdGraphNode->GetGraph(), ECastCheckedType::NullAllowed)) + { + return FlowGraph->GetFlowAsset(); + } + } + + return nullptr; +} + const FPinConnectionResponse UFlowGraphSchema::CanMergeNodes(const UEdGraphNode* NodeA, const UEdGraphNode* NodeB) const { if (IsPIESimulating()) @@ -689,13 +698,11 @@ bool UFlowGraphSchema::TryCreateConnection(UEdGraphPin* PinA, UEdGraphPin* PinB) RerouteNode->ApplyTypeFromConnectedPin(*OtherPin); - const FEdGraphPinType NewType = OtherPin->PinType; - constexpr bool bForInputPins = true; - BreakIncompatibleConnections(RerouteNode, RerouteNode->InputPins, NewType); + BreakIncompatibleConnections(RerouteNode, RerouteNode->InputPins, *OtherPin); constexpr bool bForOutputPins = false; - BreakIncompatibleConnections(RerouteNode, RerouteNode->OutputPins, NewType); + BreakIncompatibleConnections(RerouteNode, RerouteNode->OutputPins, *OtherPin); } if (EdGraph) @@ -708,7 +715,7 @@ bool UFlowGraphSchema::TryCreateConnection(UEdGraphPin* PinA, UEdGraphPin* PinB) } template -void UFlowGraphSchema::BreakIncompatibleConnections(UFlowGraphNode_Reroute* RerouteNode, const TArray& Pins, FEdGraphPinType NewType) const +void UFlowGraphSchema::BreakIncompatibleConnections(UFlowGraphNode_Reroute* RerouteNode, const TArray& Pins, const UEdGraphPin& TypeFromPin) const { // Helper function to break incompatible connections on a set of pins for (UEdGraphPin* Pin : Pins) @@ -721,12 +728,12 @@ void UFlowGraphSchema::BreakIncompatibleConnections(UFlowGraphNode_Reroute* Rero if constexpr (bIsInputPins) { // LinkedPin (output) to NewType (input) - bIsCompatible = ArePinTypesCompatible(LinkedPin->PinType, NewType, nullptr); + bIsCompatible = ArePinTypesCompatible(*LinkedPin, TypeFromPin, nullptr); } else { // NewType (output) to LinkedPin (input) - bIsCompatible = ArePinTypesCompatible(NewType, LinkedPin->PinType, nullptr); + bIsCompatible = ArePinTypesCompatible(TypeFromPin, *LinkedPin, nullptr); } if (!bIsCompatible) diff --git a/Source/FlowEditor/Private/Graph/FlowGraphSettings.cpp b/Source/FlowEditor/Private/Graph/FlowGraphSettings.cpp index 05393725..ac7e439d 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphSettings.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphSettings.cpp @@ -57,8 +57,10 @@ void UFlowGraphSettings::PostInitProperties() void UFlowGraphSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); + + const FName MemberPropertyName = PropertyChangedEvent.GetMemberPropertyName(); - if (PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED( UFlowGraphSettings, NodePrefixesToRemove )) + if (MemberPropertyName == GET_MEMBER_NAME_CHECKED( UFlowGraphSettings, NodePrefixesToRemove )) { // // We need to sort items in array, because unsorted array can cause only partial prefix removal. @@ -86,7 +88,7 @@ void UFlowGraphSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyC UFlowGraphSchema::UpdateGeneratedDisplayNames(); } } - else if (PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(UFlowGraphSettings, NodeDisplayStyles)) + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UFlowGraphSettings, NodeDisplayStyles)) { if (FlowArray::TrySortAndRemoveDuplicatesFromArrayInPlace(NodeDisplayStyles)) { diff --git a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h index 5b4ab457..93c82bfc 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h @@ -4,7 +4,7 @@ #include "EdGraph/EdGraphSchema.h" #include "Templates/SubclassOf.h" -#include "Asset/FlowPinTypeMatchPolicy.h" +#include "Policies/FlowPinTypeMatchPolicy.h" #include "FlowGraphSchema.generated.h" class UFlowAsset; @@ -68,8 +68,6 @@ class FLOWEDITOR_API UFlowGraphSchema : public UEdGraphSchema static const FFlowPinType* LookupDataPinTypeForPinCategory(const FName& PinCategory); - void EnsurePinTypesInitialized(); - bool ArePinSubCategoryObjectsCompatible( const UStruct* OutputStruct, const UStruct* InputStruct, @@ -87,7 +85,7 @@ class FLOWEDITOR_API UFlowGraphSchema : public UEdGraphSchema * * @return true if the pin types are compatible. */ - virtual bool ArePinTypesCompatible(const FEdGraphPinType& Output, const FEdGraphPinType& Input, const UClass* CallingContext = NULL, bool bIgnoreArray = false) const; + virtual bool ArePinTypesCompatible(const UEdGraphPin& OutputPin, const UEdGraphPin& InputPin, const UClass* CallingContext = NULL, bool bIgnoreArray = false) const; /** * Returns the connection response for connecting PinA to PinB, which have already been determined to be compatible @@ -118,21 +116,16 @@ class FLOWEDITOR_API UFlowGraphSchema : public UEdGraphSchema static bool IsPIESimulating(); -protected: - - /* These are the policies for matching data pin types. */ - UPROPERTY(Transient) - TMap PinTypeMatchPolicies; + static const UFlowNodeBase* GetFlowNodeBaseForPin(const UEdGraphPin& EdGraphPin); + static const UFlowAsset* GetFlowAssetForPin(const UEdGraphPin& EdGraphPin); - /* TODO (gtaylor) The mechanism for customizing PinTypeMatchPolicies will need some revision. - * I am going with a simple virtual method on schema For Now(tm) but expect a revision in how this is done, in the future. */ - virtual void InitializedPinTypes(); +protected: static UFlowGraphNode* CreateDefaultNode(UEdGraph& Graph, const TSubclassOf& NodeClass, const FVector2f& Offset, bool bPlacedAsGhostNode); /* Helper to break incompatible connections on a set of pins. */ template - void BreakIncompatibleConnections(UFlowGraphNode_Reroute* RerouteNode, const TArray& Pins, FEdGraphPinType NewType) const; + void BreakIncompatibleConnections(UFlowGraphNode_Reroute* RerouteNode, const TArray& Pins, const UEdGraphPin& TypeFromPin) const; /* Handles post-connection notifications for affected nodes. */ void NotifyNodesChanged(UFlowGraphNode* NodeA, UFlowGraphNode* NodeB, UEdGraph* Graph) const;