From 248d433add6ef0ceebc510469e123f04ad823c2d Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Wed, 25 Jun 2025 16:58:11 -0700 Subject: [PATCH] Updates from Riot version q2 2025 - Adjusted how we subscribe to external UFlowGraphNode changes - Renamed FFlowNamedDataPinOutputPropertyCustomization (which is now also used for input pins) - Added enum-based constructors for FFlowDataPinResult* structs - Fixed InstancedStruct.h reference for UE 5.4 - Added FLOW_API to GetFlowPinType() functions to allow it being called from extension plugins (eg, AIFlowGraph) - Added FDataValidationContext mutable param to ValidateNode() functions, to allow it to interoperate better with newer-style validation functions, which take the parameter. - Renamed the parameterless ValidateNode() function to DEPRECATED_ValidateNode(). This will break compiles until the name is changed OR the parameter is added and the ValidateNode() function is updated to use it - Updated all of the standard flow nodes' ValidateNode functions to use the context parameter. - Created a new UFlowNode_FormatText that formats text using input pins and FText formatting engine - Changed the UFlowNode_Log to format text (a la UFlowNode_FormatText) to generate its logged output string. - Changed UFlowNode_Log to inherit from UFlowNode_DefineProperties, so that it can have input properties added on the instance - Renamed UFlowNode_DefineProperties::OutputProperties to NamedProperties, so that it can be used as the super class for UFlowNode_FormatText - Added GetRandomSeed() function to UFlowNode. The default version uses the hash from the node's GUID. This can be overridden in subclasses (which we do) to any implementation that suits the client code. - Added an IsFinishedState() classifier function for EFlowNodeState, to error-proof checking node state for 'finished' states - Updated the ForEachAddOn templates to support a parameter to control how the function should recurse into child addons (or not). - Fixed UFlowNode_ExecuteComponent to handle injected components correctly in validation - Fixed UFlowNode_ExecuteComponent to conform to the new style of pin generation, now using ContextPins (the old method didn't work after a refactor with flow graph node reconstruction) - Updated UFlowNode_ExecuteComponent to allow the component to supply data pin output values - Updated the networking of runtime IdentityTag changes - Added UFlowAsset::GatherNodesConnectedToAllInputs helper function - Extracted UFlowNodeAddOn::FindOwningFlowNode() functionality into a function --- Config/BaseFlow.ini | 2 + Config/DefaultFlow.ini | 2 + Source/Flow/Private/AddOns/FlowNodeAddOn.cpp | 50 ++- Source/Flow/Private/FlowAsset.cpp | 70 ++- Source/Flow/Private/FlowComponent.cpp | 127 +++--- .../Actor/FlowNode_ComponentObserver.cpp | 16 +- .../Nodes/Actor/FlowNode_ExecuteComponent.cpp | 412 ++++++++++++++++-- .../Nodes/Actor/FlowNode_NotifyActor.cpp | 16 +- .../Actor/FlowNode_PlayLevelSequence.cpp | 14 +- .../Private/Nodes/Developer/FlowNode_Log.cpp | 12 +- Source/Flow/Private/Nodes/FlowNode.cpp | 2 +- Source/Flow/Private/Nodes/FlowNodeBase.cpp | 382 ++++++++++++++-- .../Nodes/Graph/FlowNode_CustomEventBase.cpp | 18 +- .../Nodes/Graph/FlowNode_DefineProperties.cpp | 56 ++- .../Nodes/Graph/FlowNode_FormatText.cpp | 115 +++++ .../Private/Nodes/Graph/FlowNode_Start.cpp | 4 +- .../Private/Nodes/Graph/FlowNode_SubGraph.cpp | 16 +- .../Private/Types/FlowDataPinProperties.cpp | 24 +- .../Types/FlowInjectComponentsHelper.cpp | 2 +- Source/Flow/Public/AddOns/FlowNodeAddOn.h | 12 + .../AddOns/FlowNodeAddOn_PredicateAND.h | 2 +- .../Public/AddOns/FlowNodeAddOn_PredicateOR.h | 2 +- Source/Flow/Public/FlowAsset.h | 9 +- Source/Flow/Public/FlowComponent.h | 16 +- Source/Flow/Public/FlowTypes.h | 24 + .../FlowDataPinPropertyProviderInterface.h | 8 +- .../Nodes/Actor/FlowNode_ComponentObserver.h | 2 +- .../Nodes/Actor/FlowNode_ExecuteComponent.h | 28 +- .../Public/Nodes/Actor/FlowNode_NotifyActor.h | 2 +- .../Nodes/Actor/FlowNode_PlayLevelSequence.h | 2 +- .../Public/Nodes/Developer/FlowNode_Log.h | 6 +- Source/Flow/Public/Nodes/FlowNode.h | 12 +- Source/Flow/Public/Nodes/FlowNodeBase.h | 50 ++- .../Nodes/Graph/FlowNode_CustomEventBase.h | 2 +- .../Nodes/Graph/FlowNode_DefineProperties.h | 6 +- .../Public/Nodes/Graph/FlowNode_FormatText.h | 42 ++ .../Public/Nodes/Graph/FlowNode_SubGraph.h | 2 +- .../Flow/Public/Types/FlowDataPinProperties.h | 127 ++++-- Source/Flow/Public/Types/FlowDataPinResults.h | 16 +- .../Private/Asset/FlowObjectDiff.cpp | 3 +- ...amedDataPinOutputPropertyCustomization.cpp | 6 +- .../FlowEditor/Private/FlowEditorModule.cpp | 2 +- .../Private/Graph/FlowGraphSchema.cpp | 1 + .../Private/Graph/Nodes/FlowGraphNode.cpp | 31 +- .../Widgets/SGraphEditorActionMenuFlow.cpp | 3 + ...wNamedDataPinOutputPropertyCustomization.h | 4 +- Source/FlowEditor/Public/FlowEditorModule.h | 1 + .../Graph/FlowGraphConnectionDrawingPolicy.h | 1 + .../FlowEditor/Public/Graph/FlowGraphEditor.h | 1 + .../FlowEditor/Public/Graph/FlowGraphSchema.h | 2 + .../Public/Graph/Nodes/FlowGraphNode.h | 1 + 51 files changed, 1477 insertions(+), 289 deletions(-) create mode 100644 Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp create mode 100644 Source/Flow/Public/Nodes/Graph/FlowNode_FormatText.h diff --git a/Config/BaseFlow.ini b/Config/BaseFlow.ini index 9e22e6ad2..fe5504715 100644 --- a/Config/BaseFlow.ini +++ b/Config/BaseFlow.ini @@ -2,3 +2,5 @@ +ClassRedirects=(OldName="/Script/Flow.FlowNode_CustomEvent",NewName="/Script/Flow.FlowNode_CustomInput") +PropertyRedirects=(OldName="FlowAsset.CustomEvents",NewName="FlowAsset.CustomInputs") +PropertyRedirects=(OldName="FlowGraphNode.FlowNode",NewName="FlowGraphNode.NodeInstance") ++StructRedirects=(OldName="/Script/Flow.FlowNamedDataPinOutputProperty",NewName="/Script/Flow.FlowNamedDataPinProperty") ++PropertyRedirects=(OldName="FlowNode_DefineProperties.OutputProperties",NewName="NamedProperties") diff --git a/Config/DefaultFlow.ini b/Config/DefaultFlow.ini index 9e22e6ad2..fe5504715 100644 --- a/Config/DefaultFlow.ini +++ b/Config/DefaultFlow.ini @@ -2,3 +2,5 @@ +ClassRedirects=(OldName="/Script/Flow.FlowNode_CustomEvent",NewName="/Script/Flow.FlowNode_CustomInput") +PropertyRedirects=(OldName="FlowAsset.CustomEvents",NewName="FlowAsset.CustomInputs") +PropertyRedirects=(OldName="FlowGraphNode.FlowNode",NewName="FlowGraphNode.NodeInstance") ++StructRedirects=(OldName="/Script/Flow.FlowNamedDataPinOutputProperty",NewName="/Script/Flow.FlowNamedDataPinProperty") ++PropertyRedirects=(OldName="FlowNode_DefineProperties.OutputProperties",NewName="NamedProperties") diff --git a/Source/Flow/Private/AddOns/FlowNodeAddOn.cpp b/Source/Flow/Private/AddOns/FlowNodeAddOn.cpp index 996f2745a..1f665934a 100644 --- a/Source/Flow/Private/AddOns/FlowNodeAddOn.cpp +++ b/Source/Flow/Private/AddOns/FlowNodeAddOn.cpp @@ -3,6 +3,8 @@ #include "AddOns/FlowNodeAddOn.h" #include "FlowLogChannels.h" +#include "Engine/World.h" + #include "Nodes/FlowNode.h" #include "Misc/RuntimeErrors.h" @@ -72,6 +74,35 @@ UFlowNode* UFlowNodeAddOn::GetFlowNode() const return FlowNode; } +UFlowNode* UFlowNodeAddOn::FindOwningFlowNode() const +{ + UObject* OuterObject = GetOuter(); + UFlowNode* ParentFlowNode = nullptr; + + while (IsValid(OuterObject)) + { + ParentFlowNode = Cast(OuterObject); + if (ParentFlowNode) + { + break; + } + + OuterObject = OuterObject->GetOuter(); + } + + return ParentFlowNode; +} + +int32 UFlowNodeAddOn::GetRandomSeed() const +{ + if (ensure(FlowNode)) + { + return FlowNode->GetRandomSeed(); + } + + return 0; +} + bool UFlowNodeAddOn::IsSupportedInputPinName(const FName& PinName) const { if (InputPins.IsEmpty()) @@ -91,18 +122,8 @@ bool UFlowNodeAddOn::IsSupportedInputPinName(const FName& PinName) const void UFlowNodeAddOn::CacheFlowNode() { - UObject* OuterObject = GetOuter(); - while (IsValid(OuterObject)) - { - FlowNode = Cast(OuterObject); - if (FlowNode) - { - break; - } - - OuterObject = OuterObject->GetOuter(); - } - + FlowNode = FindOwningFlowNode(); + ensureAsRuntimeWarning(FlowNode); } @@ -137,4 +158,9 @@ TArray UFlowNodeAddOn::GetContextOutputs() const { return GetPinsForContext(OutputPins); } + +void UFlowNodeAddOn::RequestReconstructionOnOwningFlowNode() const +{ + (void) OnAddOnRequestedParentReconstruction.ExecuteIfBound(); +} #endif // WITH_EDITOR diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 8671016c5..b48861815 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -21,6 +21,7 @@ #if WITH_EDITOR #include "Editor.h" #include "Editor/EditorEngine.h" +#include "Misc/DataValidation.h" FString UFlowAsset::ValidationError_NodeClassNotAllowed = TEXT("Node class {0} is not allowed in this asset."); FString UFlowAsset::ValidationError_NullNodeInstance = TEXT("Node with GUID {0} is NULL"); @@ -105,6 +106,9 @@ void UFlowAsset::PostLoad() EDataValidationResult UFlowAsset::ValidateAsset(FFlowMessageLog& MessageLog) { + // TODO (gtaylor) We should further refactor the Flow validation to use FDataValidationContext throughout + FDataValidationContext Context; + // validate nodes for (const TPair& Node : ObjectPtrDecay(Nodes)) { @@ -122,9 +126,43 @@ EDataValidationResult UFlowAsset::ValidateAsset(FFlowMessageLog& MessageLog) } Node.Value->ValidationLog.Messages.Empty(); - if (Node.Value->ValidateNode() == EDataValidationResult::Invalid) + + if (Node.Value->ValidateNodeAndAddOns(Context) == EDataValidationResult::Invalid) { - MessageLog.Messages.Append(Node.Value->ValidationLog.Messages); + // Convert the issues from UE format into Flow's expected format + for (const FDataValidationContext::FIssue& Issue : Context.GetIssues()) + { + switch (Issue.Severity) + { + case EMessageSeverity::Error: + { + MessageLog.Error(*Issue.Message.ToString(), Node.Value); + } + break; + + case EMessageSeverity::PerformanceWarning: + case EMessageSeverity::Warning: + { + MessageLog.Warning(*Issue.Message.ToString(), Node.Value); + } + break; + + case EMessageSeverity::Info: + { + MessageLog.Note(*Issue.Message.ToString(), Node.Value); + } + break; + + default: + { + const FString UnhandledSeverityString = FString::Printf(TEXT("Unhandled EMessageSeverity value %d! The code needs to be updated."), Issue.Severity); + MessageLog.Error(*UnhandledSeverityString, Node.Value); + + MessageLog.Error(*Issue.Message.ToString(), Node.Value); + } + break; + } + } } } else @@ -224,7 +262,7 @@ bool UFlowAsset::CanFlowAssetUseFlowNodeClass(const UClass& FlowNodeClass) const bool UFlowAsset::IsFlowNodeClassInDeniedClasses(const UClass& FlowNodeClass) const { - for (const TSubclassOf& DeniedNodeClass : DeniedNodeClasses) + for (const TSubclassOf DeniedNodeClass : DeniedNodeClasses) { if (DeniedNodeClass && FlowNodeClass.IsChildOf(DeniedNodeClass)) { @@ -239,13 +277,12 @@ bool UFlowAsset::IsFlowNodeClassInDeniedClasses(const UClass& FlowNodeClass) con return false; } -bool UFlowAsset::IsFlowNodeClassInAllowedClasses(const UClass& FlowNodeClass, - const TSubclassOf& RequiredAncestor) const +bool UFlowAsset::IsFlowNodeClassInAllowedClasses(const UClass& FlowNodeClass, const TSubclassOf RequiredAncestor) const { if (AllowedNodeClasses.Num() > 0) { bool bAllowedInAsset = false; - for (const TSubclassOf& AllowedNodeClass : AllowedNodeClasses) + for (const TSubclassOf AllowedNodeClass : AllowedNodeClasses) { // If a RequiredAncestor is provided, the AllowedNodeClass must be a subclass of the RequiredAncestor if (AllowedNodeClass && FlowNodeClass.IsChildOf(AllowedNodeClass) && (!RequiredAncestor || AllowedNodeClass->IsChildOf(RequiredAncestor))) @@ -1030,6 +1067,27 @@ TArray UFlowAsset::GetNodesInExecutionOrder(UFlowNode* FirstIterated return FoundNodes; } +TArray UFlowAsset::GatherNodesConnectedToAllInputs() const +{ + TSet> IteratedNodes; + TArray ConnectedNodes; + + // Nodes connected to the Start node + UFlowNode* DefaultEntryNode = GetDefaultEntryNode(); + GetNodesInExecutionOrder_Recursive(DefaultEntryNode, IteratedNodes, ConnectedNodes); + + // Nodes connected to Custom Input node(s) + for (const TPair& Node : ObjectPtrDecay(Nodes)) + { + if (UFlowNode_CustomInput* CustomInput = Cast(Node.Value)) + { + GetNodesInExecutionOrder_Recursive(CustomInput, IteratedNodes, ConnectedNodes); + } + } + + return ConnectedNodes; +} + void UFlowAsset::AddInstance(UFlowAsset* Instance) { ActiveInstances.Add(Instance); diff --git a/Source/Flow/Private/FlowComponent.cpp b/Source/Flow/Private/FlowComponent.cpp index fc1dc359e..5e4eaabd5 100644 --- a/Source/Flow/Private/FlowComponent.cpp +++ b/Source/Flow/Private/FlowComponent.cpp @@ -39,13 +39,15 @@ void UFlowComponent::GetLifetimeReplicatedProps(TArray& OutLi FDoRepLifetimeParams Params; Params.bIsPushBased = true; - DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, IdentityTags, Params); + DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, AddedIdentityTags, Params); + DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, RemovedIdentityTags, Params); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, RecentlySentNotifyTags, Params); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, NotifyTagsFromGraph, Params); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, NotifyTagsFromAnotherComponent, Params); #else - DOREPLIFETIME(ThisClass, IdentityTags); + DOREPLIFETIME(ThisClass, AddedIdentityTags); + DOREPLIFETIME(ThisClass, RemovedIdentityTags); DOREPLIFETIME(ThisClass, RecentlySentNotifyTags); DOREPLIFETIME(ThisClass, NotifyTagsFromGraph); @@ -112,12 +114,7 @@ void UFlowComponent::AddIdentityTag(const FGameplayTag Tag, const EFlowNetMode N if (IsFlowNetMode(NetMode) && Tag.IsValid() && !IdentityTags.HasTagExact(Tag)) { IdentityTags.AddTag(Tag); -#if WITH_PUSH_MODEL - if (GetNetMode() < NM_Client) - { - MARK_PROPERTY_DIRTY_FROM_NAME(UFlowComponent, IdentityTags, this); - } -#endif + if (HasBegunPlay()) { OnIdentityTagsAdded.Broadcast(this, FGameplayTagContainer(Tag)); @@ -126,6 +123,14 @@ void UFlowComponent::AddIdentityTag(const FGameplayTag Tag, const EFlowNetMode N { FlowSubsystem->OnIdentityTagAdded(this, Tag); } + + if (IsNetMode(NM_DedicatedServer) || IsNetMode(NM_ListenServer)) + { + AddedIdentityTags = FGameplayTagContainer(Tag); +#if WITH_PUSH_MODEL + MARK_PROPERTY_DIRTY_FROM_NAME(UFlowComponent, AddedIdentityTags, this); +#endif + } } } } @@ -145,22 +150,21 @@ void UFlowComponent::AddIdentityTags(FGameplayTagContainer Tags, const EFlowNetM } } - if (ValidatedTags.Num() > 0) + if (ValidatedTags.Num() > 0 && HasBegunPlay()) { -#if WITH_PUSH_MODEL - if (GetNetMode() < NM_Client) + OnIdentityTagsAdded.Broadcast(this, ValidatedTags); + + if (UFlowSubsystem* FlowSubsystem = GetFlowSubsystem()) { - MARK_PROPERTY_DIRTY_FROM_NAME(UFlowComponent, IdentityTags, this); + FlowSubsystem->OnIdentityTagsAdded(this, ValidatedTags); } -#endif - if (HasBegunPlay()) - { - OnIdentityTagsAdded.Broadcast(this, ValidatedTags); - if (UFlowSubsystem* FlowSubsystem = GetFlowSubsystem()) - { - FlowSubsystem->OnIdentityTagsAdded(this, ValidatedTags); - } + if (IsNetMode(NM_DedicatedServer) || IsNetMode(NM_ListenServer)) + { + AddedIdentityTags = ValidatedTags; +#if WITH_PUSH_MODEL + MARK_PROPERTY_DIRTY_FROM_NAME(UFlowComponent, AddedIdentityTags, this); +#endif } } } @@ -171,12 +175,7 @@ void UFlowComponent::RemoveIdentityTag(const FGameplayTag Tag, const EFlowNetMod if (IsFlowNetMode(NetMode) && Tag.IsValid() && IdentityTags.HasTagExact(Tag)) { IdentityTags.RemoveTag(Tag); -#if WITH_PUSH_MODEL - if (GetNetMode() < NM_Client) - { - MARK_PROPERTY_DIRTY_FROM_NAME(UFlowComponent, IdentityTags, this); - } -#endif + if (HasBegunPlay()) { OnIdentityTagsRemoved.Broadcast(this, FGameplayTagContainer(Tag)); @@ -185,6 +184,14 @@ void UFlowComponent::RemoveIdentityTag(const FGameplayTag Tag, const EFlowNetMod { FlowSubsystem->OnIdentityTagRemoved(this, Tag); } + + if (IsNetMode(NM_DedicatedServer) || IsNetMode(NM_ListenServer)) + { + RemovedIdentityTags = FGameplayTagContainer(Tag); +#if WITH_PUSH_MODEL + MARK_PROPERTY_DIRTY_FROM_NAME(UFlowComponent, RemovedIdentityTags, this); +#endif + } } } } @@ -204,67 +211,45 @@ void UFlowComponent::RemoveIdentityTags(FGameplayTagContainer Tags, const EFlowN } } - if (ValidatedTags.Num() > 0) + if (ValidatedTags.Num() > 0 && HasBegunPlay()) { -#if WITH_PUSH_MODEL - if (GetNetMode() < NM_Client) + OnIdentityTagsRemoved.Broadcast(this, ValidatedTags); + + if (UFlowSubsystem* FlowSubsystem = GetWorld()->GetGameInstance()->GetSubsystem()) { - MARK_PROPERTY_DIRTY_FROM_NAME(UFlowComponent, IdentityTags, this); + FlowSubsystem->OnIdentityTagsRemoved(this, ValidatedTags); } -#endif - if (HasBegunPlay()) - { - OnIdentityTagsRemoved.Broadcast(this, ValidatedTags); - if (UFlowSubsystem* FlowSubsystem = GetWorld()->GetGameInstance()->GetSubsystem()) - { - FlowSubsystem->OnIdentityTagsRemoved(this, ValidatedTags); - } + if (IsNetMode(NM_DedicatedServer) || IsNetMode(NM_ListenServer)) + { + RemovedIdentityTags = ValidatedTags; +#if WITH_PUSH_MODEL + MARK_PROPERTY_DIRTY_FROM_NAME(UFlowComponent, RemovedIdentityTags, this); +#endif } } } } -void UFlowComponent::OnRep_IdentityTags(const FGameplayTagContainer& PreviousTags) +void UFlowComponent::OnRep_AddedIdentityTags() { + IdentityTags.AppendTags(AddedIdentityTags); + OnIdentityTagsAdded.Broadcast(this, AddedIdentityTags); - // Any tags that are now in the IdentityTags container but haven't been previously must have been added. - FGameplayTagContainer AddedTags; - for (const FGameplayTag& Tag : IdentityTags) + if (UFlowSubsystem* FlowSubsystem = GetFlowSubsystem()) { - if (!PreviousTags.HasTagExact(Tag)) - { - AddedTags.AddTag(Tag); - } + FlowSubsystem->OnIdentityTagsAdded(this, AddedIdentityTags); } +} - if (AddedTags.Num() > 0) - { - OnIdentityTagsAdded.Broadcast(this, AddedTags); - - if (UFlowSubsystem* FlowSubsystem = GetFlowSubsystem()) - { - FlowSubsystem->OnIdentityTagsAdded(this, AddedTags); - } - } +void UFlowComponent::OnRep_RemovedIdentityTags() +{ + IdentityTags.RemoveTags(RemovedIdentityTags); + OnIdentityTagsRemoved.Broadcast(this, RemovedIdentityTags); - // Any tags that have been in the IdentityTags container previously but aren't in it anymore after the replication update must have been removed. - FGameplayTagContainer RemovedTags; - for (const FGameplayTag& Tag : PreviousTags) + if (UFlowSubsystem* FlowSubsystem = GetWorld()->GetGameInstance()->GetSubsystem()) { - if (!IdentityTags.HasTagExact(Tag)) - { - RemovedTags.AddTag(Tag); - } - } - if (RemovedTags.Num() > 0) - { - OnIdentityTagsRemoved.Broadcast(this, RemovedTags); - - if (UFlowSubsystem* FlowSubsystem = GetFlowSubsystem()) - { - FlowSubsystem->OnIdentityTagsRemoved(this, RemovedTags); - } + FlowSubsystem->OnIdentityTagsRemoved(this, RemovedIdentityTags); } } diff --git a/Source/Flow/Private/Nodes/Actor/FlowNode_ComponentObserver.cpp b/Source/Flow/Private/Nodes/Actor/FlowNode_ComponentObserver.cpp index 10d2ef8d3..9924db07c 100644 --- a/Source/Flow/Private/Nodes/Actor/FlowNode_ComponentObserver.cpp +++ b/Source/Flow/Private/Nodes/Actor/FlowNode_ComponentObserver.cpp @@ -2,6 +2,9 @@ #include "Nodes/Actor/FlowNode_ComponentObserver.h" #include "FlowSubsystem.h" +#if WITH_EDITOR +#include "Misc/DataValidation.h" +#endif #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_ComponentObserver) @@ -155,15 +158,20 @@ FString UFlowNode_ComponentObserver::GetNodeDescription() const return GetIdentityTagsDescription(IdentityTags); } -EDataValidationResult UFlowNode_ComponentObserver::ValidateNode() +EDataValidationResult UFlowNode_ComponentObserver::ValidateNode(FDataValidationContext& Context) const { + const EDataValidationResult SuperResult = Super::ValidateNode(Context); + + EDataValidationResult FinalResult = CombineDataValidationResults(SuperResult, EDataValidationResult::Valid); + if (IdentityTags.IsEmpty()) { - ValidationLog.Error(*UFlowNode::MissingIdentityTag, this); - return EDataValidationResult::Invalid; + Context.AddError(FText::FromString(UFlowNode::MissingIdentityTag)); + + FinalResult = CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } - return EDataValidationResult::Valid; + return FinalResult; } FString UFlowNode_ComponentObserver::GetStatusString() const diff --git a/Source/Flow/Private/Nodes/Actor/FlowNode_ExecuteComponent.cpp b/Source/Flow/Private/Nodes/Actor/FlowNode_ExecuteComponent.cpp index ae0ee254c..8d145c67f 100644 --- a/Source/Flow/Private/Nodes/Actor/FlowNode_ExecuteComponent.cpp +++ b/Source/Flow/Private/Nodes/Actor/FlowNode_ExecuteComponent.cpp @@ -11,6 +11,9 @@ #include "Types/FlowInjectComponentsManager.h" #include "GameFramework/Actor.h" #include "Components/ActorComponent.h" +#if WITH_EDITOR +#include "Misc/DataValidation.h" +#endif #define LOCTEXT_NAMESPACE "FlowNode" @@ -22,6 +25,9 @@ UFlowNode_ExecuteComponent::UFlowNode_ExecuteComponent() #if WITH_EDITOR Category = TEXT("Actor"); #endif + + InputPins.Reset(); + OutputPins.Reset(); } void UFlowNode_ExecuteComponent::InitializeInstance() @@ -193,6 +199,345 @@ void UFlowNode_ExecuteComponent::ExecuteInput(const FName& PinName) { LogError(FString::Printf(TEXT("Could not ExecuteInput %s, because the component was missing or could not be resolved."), *PinName.ToString())); } + + // Trigger the default output (if the output pins weren't replaced, + // and the node hasn't already Finished) + if (OutputPins.Contains(UFlowNode::DefaultOutputPin.PinName) && !HasFinished()) + { + constexpr bool bFinish = false; + TriggerOutput(UFlowNode::DefaultOutputPin.PinName, bFinish); + } +} + +#if WITH_EDITOR +TArray UFlowNode_ExecuteComponent::GetContextInputs() const +{ + TArray ContextInputs; + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (const IFlowContextPinSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + ContextInputs = PinSupplierInterface->GetContextInputs(); + } + } + else if (const UActorComponent* ExpectedComponent = TryGetExpectedComponent()) + { + if (const IFlowContextPinSupplierInterface* PinSupplierInterface = Cast(ExpectedComponent)) + { + ContextInputs = PinSupplierInterface->GetContextInputs(); + } + } + + if (ContextInputs.IsEmpty()) + { + // Add the default input if none are desired by the component + ContextInputs.Add(UFlowNode::DefaultInputPin); + } + + ContextInputs.Append(Super::GetContextInputs()); + + return ContextInputs; +} + +TArray UFlowNode_ExecuteComponent::GetContextOutputs() const +{ + TArray ContextOutputs; + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (const IFlowContextPinSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + ContextOutputs = PinSupplierInterface->GetContextOutputs(); + } + } + else if (const UActorComponent* ExpectedComponent = TryGetExpectedComponent()) + { + if (const IFlowContextPinSupplierInterface* PinSupplierInterface = Cast(ExpectedComponent)) + { + ContextOutputs = PinSupplierInterface->GetContextOutputs(); + } + } + + if (ContextOutputs.IsEmpty()) + { + // Add the default output if none are desired by the component + ContextOutputs.Add(UFlowNode::DefaultOutputPin); + } + + ContextOutputs.Append(Super::GetContextOutputs()); + + return ContextOutputs; +} +#endif // WITH_EDITOR + +bool UFlowNode_ExecuteComponent::CanSupplyDataPinValues_Implementation() const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + if (IFlowDataPinValueSupplierInterface::Execute_CanSupplyDataPinValues(ResolvedComp)) + { + return true; + } + } + } + + return Super::CanSupplyDataPinValues_Implementation(); +} + +FFlowDataPinResult_Bool UFlowNode_ExecuteComponent::TrySupplyDataPinAsBool_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Bool PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsBool(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsBool_Implementation(PinName); +} + +FFlowDataPinResult_Int UFlowNode_ExecuteComponent::TrySupplyDataPinAsInt_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Int PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsInt(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsInt_Implementation(PinName); +} + +FFlowDataPinResult_Float UFlowNode_ExecuteComponent::TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Float PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsFloat(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsFloat_Implementation(PinName); +} + +FFlowDataPinResult_Name UFlowNode_ExecuteComponent::TrySupplyDataPinAsName_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Name PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsName(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsName_Implementation(PinName); +} + +FFlowDataPinResult_String UFlowNode_ExecuteComponent::TrySupplyDataPinAsString_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_String PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsString(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsString_Implementation(PinName); +} + +FFlowDataPinResult_Text UFlowNode_ExecuteComponent::TrySupplyDataPinAsText_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Text PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsText(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsText_Implementation(PinName); +} + +FFlowDataPinResult_Enum UFlowNode_ExecuteComponent::TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Enum PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsEnum(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsEnum_Implementation(PinName); +} + +FFlowDataPinResult_Vector UFlowNode_ExecuteComponent::TrySupplyDataPinAsVector_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Vector PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsVector(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsVector_Implementation(PinName); +} + +FFlowDataPinResult_Rotator UFlowNode_ExecuteComponent::TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Rotator PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsRotator(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsRotator_Implementation(PinName); +} + +FFlowDataPinResult_Transform UFlowNode_ExecuteComponent::TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Transform PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsTransform(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsTransform_Implementation(PinName); +} + +FFlowDataPinResult_GameplayTag UFlowNode_ExecuteComponent::TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_GameplayTag PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsGameplayTag(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsGameplayTag_Implementation(PinName); +} + +FFlowDataPinResult_GameplayTagContainer UFlowNode_ExecuteComponent::TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_GameplayTagContainer PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsGameplayTagContainer(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsGameplayTagContainer_Implementation(PinName); +} + +FFlowDataPinResult_InstancedStruct UFlowNode_ExecuteComponent::TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_InstancedStruct PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsInstancedStruct(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsInstancedStruct_Implementation(PinName); +} + +FFlowDataPinResult_Object UFlowNode_ExecuteComponent::TrySupplyDataPinAsObject_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Object PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsObject(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsObject_Implementation(PinName); +} + +FFlowDataPinResult_Class UFlowNode_ExecuteComponent::TrySupplyDataPinAsClass_Implementation(const FName& PinName) const +{ + if (UActorComponent* ResolvedComp = GetResolvedComponent()) + { + if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) + { + const FFlowDataPinResult_Class PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsClass(ResolvedComp, PinName); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + return PinResult; + } + } + } + + return Super::TrySupplyDataPinAsClass_Implementation(PinName); } bool UFlowNode_ExecuteComponent::TryInjectComponent() @@ -306,6 +651,19 @@ UActorComponent* UFlowNode_ExecuteComponent::TryResolveComponent() return ResolvedComp; } +UActorComponent* UFlowNode_ExecuteComponent::GetResolvedComponent() const +{ + // This version of the function assumes the component has already been resolved previously. + // (using TryResolveComponent) + UActorComponent* ResolvedComp = ComponentRef.GetResolvedComponent(); + if (IsValid(ResolvedComp)) + { + return ResolvedComp; + } + + return nullptr; +} + #if WITH_EDITOR const UActorComponent* UFlowNode_ExecuteComponent::TryGetExpectedComponent() const { @@ -381,45 +739,34 @@ void UFlowNode_ExecuteComponent::RefreshComponentSource() void UFlowNode_ExecuteComponent::RefreshPins() { - bool bChangedPins = false; - - const UActorComponent* ExpectedComponent = TryGetExpectedComponent(); - if (const IFlowContextPinSupplierInterface* ContextPinSupplierInterface = Cast(ExpectedComponent)) - { - const TArray NewInputPins = ContextPinSupplierInterface->GetContextInputs(); - bChangedPins = RebuildPinArray(NewInputPins, InputPins, DefaultInputPin) || bChangedPins; + OnReconstructionRequested.ExecuteIfBound(); +} - const TArray NewOutputPins = ContextPinSupplierInterface->GetContextOutputs(); - bChangedPins = RebuildPinArray(NewOutputPins, OutputPins, DefaultOutputPin) || bChangedPins; - } - else - { - bChangedPins = RebuildPinArray(TArray(&DefaultInputPin.PinName, 1), InputPins, DefaultInputPin) || bChangedPins; - bChangedPins = RebuildPinArray(TArray(&DefaultOutputPin.PinName, 1), OutputPins, DefaultOutputPin) || bChangedPins; - } +EDataValidationResult UFlowNode_ExecuteComponent::ValidateNode(FDataValidationContext& Context) const +{ + const EDataValidationResult SuperResult = Super::ValidateNode(Context); - if (bChangedPins) + EDataValidationResult FinalResult = CombineDataValidationResults(SuperResult, EDataValidationResult::Valid); + + if (IsValid(ComponentTemplate) || IsValid(ComponentClass)) { - OnReconstructionRequested.ExecuteIfBound(); + return FinalResult; } -} - -EDataValidationResult UFlowNode_ExecuteComponent::ValidateNode() -{ + const bool bHasComponent = ComponentRef.IsConfigured(); if (!bHasComponent) { - ValidationLog.Error(TEXT("ExectuteComponent requires a valid Compoennt reference"), this); + Context.AddError(FText::FromString(TEXT("ExectuteComponent requires a valid Compoennt reference"))); - return EDataValidationResult::Invalid; + return CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } const TSubclassOf ExpectedActorOwnerClass = TryGetExpectedActorOwnerClass(); if (!IsValid(ExpectedActorOwnerClass)) { - ValidationLog.Error(TEXT("Invalid or null Expected Actor Owner Class for this Flow Asset"), this); + Context.AddError(FText::FromString(TEXT("Invalid or null Expected Actor Owner Class for this Flow Asset"))); - return EDataValidationResult::Invalid; + return CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } { @@ -427,28 +774,29 @@ EDataValidationResult UFlowNode_ExecuteComponent::ValidateNode() const UActorComponent* ExpectedComponent = TryGetExpectedComponent(); if (!IsValid(ExpectedComponent)) { - ValidationLog.Error(TEXT("Could not resolve component for flow actor owner"), this); + Context.AddError(FText::FromString(TEXT("Could not resolve component for flow actor owner"))); - return EDataValidationResult::Invalid; + return CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } // Check that the component implements the expected interfaces if (!Cast(ExpectedComponent)) { - ValidationLog.Error(TEXT("Expected component to implement IFlowExternalExecutableInterface"), this); + Context.AddError(FText::FromString(TEXT("Expected component to implement IFlowExternalExecutableInterface"))); - return EDataValidationResult::Invalid; + return CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } if (!Cast(ExpectedComponent)) { - ValidationLog.Error(TEXT("Expected component to implement IFlowCoreExecutableInterface"), this); + Context.AddError(FText::FromString(TEXT("Expected component to implement IFlowCoreExecutableInterface"))); - return EDataValidationResult::Invalid; + return CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } } - return EDataValidationResult::Valid; + + return FinalResult; } FString UFlowNode_ExecuteComponent::GetStatusString() const diff --git a/Source/Flow/Private/Nodes/Actor/FlowNode_NotifyActor.cpp b/Source/Flow/Private/Nodes/Actor/FlowNode_NotifyActor.cpp index 9421a0353..af85b315e 100644 --- a/Source/Flow/Private/Nodes/Actor/FlowNode_NotifyActor.cpp +++ b/Source/Flow/Private/Nodes/Actor/FlowNode_NotifyActor.cpp @@ -6,6 +6,9 @@ #include "Engine/GameInstance.h" #include "Engine/World.h" +#if WITH_EDITOR +#include "Misc/DataValidation.h" +#endif #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_NotifyActor) @@ -39,14 +42,19 @@ FString UFlowNode_NotifyActor::GetNodeDescription() const return GetIdentityTagsDescription(IdentityTags) + LINE_TERMINATOR + GetNotifyTagsDescription(NotifyTags); } -EDataValidationResult UFlowNode_NotifyActor::ValidateNode() +EDataValidationResult UFlowNode_NotifyActor::ValidateNode(FDataValidationContext& Context) const { + const EDataValidationResult SuperResult = Super::ValidateNode(Context); + + EDataValidationResult FinalResult = CombineDataValidationResults(SuperResult, EDataValidationResult::Valid); + if (IdentityTags.IsEmpty()) { - ValidationLog.Error(*UFlowNode::MissingIdentityTag, this); - return EDataValidationResult::Invalid; + Context.AddError(FText::FromString(UFlowNode::MissingIdentityTag)); + + FinalResult = CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } - return EDataValidationResult::Valid; + return FinalResult; } #endif diff --git a/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp b/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp index 9cfbd4f2b..42601d100 100644 --- a/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp +++ b/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp @@ -8,6 +8,7 @@ #include "LevelSequence/FlowLevelSequencePlayer.h" #if WITH_EDITOR +#include "Misc/DataValidation.h" #include "MovieScene/MovieSceneFlowTrack.h" #include "MovieScene/MovieSceneFlowTriggerSection.h" #endif @@ -320,15 +321,20 @@ FString UFlowNode_PlayLevelSequence::GetNodeDescription() const return Sequence.IsNull() ? TEXT("[No sequence]") : Sequence.GetAssetName(); } -EDataValidationResult UFlowNode_PlayLevelSequence::ValidateNode() +EDataValidationResult UFlowNode_PlayLevelSequence::ValidateNode(FDataValidationContext& Context) const { + const EDataValidationResult SuperResult = Super::ValidateNode(Context); + + EDataValidationResult FinalResult = CombineDataValidationResults(SuperResult, EDataValidationResult::Valid); + if (Sequence.IsNull()) { - ValidationLog.Error(TEXT("Level Sequence asset not assigned or invalid!"), this); - return EDataValidationResult::Invalid; + Context.AddError(FText::FromString(FString::Printf(TEXT("Level Sequence asset not assigned or invalid!"), this))); + + FinalResult = CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } - return EDataValidationResult::Valid; + return FinalResult; } FString UFlowNode_PlayLevelSequence::GetStatusString() const diff --git a/Source/Flow/Private/Nodes/Developer/FlowNode_Log.cpp b/Source/Flow/Private/Nodes/Developer/FlowNode_Log.cpp index e531b4d57..5a6f66d0a 100644 --- a/Source/Flow/Private/Nodes/Developer/FlowNode_Log.cpp +++ b/Source/Flow/Private/Nodes/Developer/FlowNode_Log.cpp @@ -2,7 +2,6 @@ #include "Nodes/Developer/FlowNode_Log.h" #include "FlowLogChannels.h" -#include "FlowSettings.h" #include "Engine/Engine.h" @@ -22,6 +21,9 @@ UFlowNode_Log::UFlowNode_Log(const FObjectInitializer& ObjectInitializer) Category = TEXT("Developer"); NodeDisplayStyle = FlowNodeStyle::Developer; #endif + + InputPins = { UFlowNode::DefaultInputPin }; + OutputPins = { UFlowNode::DefaultOutputPin }; } void UFlowNode_Log::ExecuteInput(const FName& PinName) @@ -36,6 +38,14 @@ void UFlowNode_Log::ExecuteInput(const FName& PinName) MessageResult.SetValue(Message); } + // Format Message with named properties + FText FormattedText; + if (TryFormatTextWithNamedPropertiesAsParameters(FText::FromString(MessageResult.Value), FormattedText)) + { + MessageResult.Value = FormattedText.ToString(); + } + + // Display the message check(MessageResult.Result == EFlowDataPinResolveResult::Success); switch (Verbosity) diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index b53c2a9a4..048339277 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -853,7 +853,7 @@ void UFlowNode::TriggerFirstOutput(const bool bFinish) void UFlowNode::TriggerOutput(const FName PinName, const bool bFinish /*= false*/, const EFlowPinActivationType ActivationType /*= Default*/) { - if (ActivationState == EFlowNodeState::Completed || ActivationState == EFlowNodeState::Aborted) + if (HasFinished()) { // do not trigger output if node is already finished or aborted LogError(TEXT("Trying to TriggerOutput after finished or aborted")); diff --git a/Source/Flow/Private/Nodes/FlowNodeBase.cpp b/Source/Flow/Private/Nodes/FlowNodeBase.cpp index 5d2b15899..0019db8d8 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -8,10 +8,14 @@ #include "FlowSubsystem.h" #include "FlowTypes.h" #include "Interfaces/FlowDataPinValueSupplierInterface.h" +#include "Types/FlowArray.h" #include "Components/ActorComponent.h" + #if WITH_EDITOR #include "Editor.h" +#include "Logging/TokenizedMessage.h" +#include "Misc/DataValidation.h" #endif #include "Engine/Blueprint.h" @@ -263,6 +267,62 @@ FString UFlowNodeBase::GetStatusString() const { return K2_GetStatusString(); } + +EDataValidationResult UFlowNodeBase::ValidateNodeAndAddOns(FDataValidationContext& Context) const +{ + const EDataValidationResult ThisNodeResult = ValidateNode(Context); + + EDataValidationResult FinalResult = ThisNodeResult; + + for (const UFlowNodeAddOn* AddOn : AddOns) + { + if (IsValid(AddOn)) + { + const EDataValidationResult AddOnResult = AddOn->ValidateNodeAndAddOns(Context); + + FinalResult = CombineDataValidationResults(FinalResult, AddOnResult); + } + } + + // Deprecated results + if (const UFlowNode* ThisAsConstFlowNode = Cast(this)) + { + UFlowNode* ThisAsMutableFlowNode = const_cast(ThisAsConstFlowNode); + + const EDataValidationResult DeprecatedResult = ThisAsMutableFlowNode->DEPRECATED_ValidateNode(); + + FinalResult = CombineDataValidationResults(FinalResult, DeprecatedResult); + + if (DeprecatedResult == EDataValidationResult::Invalid) + { + // Add all of the ValidationLog entries to the Context + for (const TSharedRef& Message : ThisAsMutableFlowNode->ValidationLog.Messages) + { + switch (Message->GetSeverity()) + { + case EMessageSeverity::Type::Error: + case EMessageSeverity::Type::Warning: + case EMessageSeverity::Type::PerformanceWarning: + case EMessageSeverity::Type::Info: + break; + + default: + { + Context.AddError( + FText::FromString( + FString::Printf(TEXT("Unhandled EMessageSeverity value %d! The code needs to be updated."), Message->GetSeverity()))); + } + break; + } + + Context.AddMessage(Message); + } + } + } + + return FinalResult; +} + #endif // WITH_EDITOR UFlowAsset* UFlowNodeBase::GetFlowAsset() const @@ -351,6 +411,28 @@ IFlowOwnerInterface* UFlowNodeBase::GetFlowOwnerInterface() const return nullptr; } +TArray UFlowNodeBase::BuildFlowNodeBaseAncestorChain(UFlowNodeBase& FromFlowNodeBase, bool bIncludeFromFlowNodeBase) +{ + TArray AncestorChain; + + UFlowNodeBase* CurOuter = Cast(FromFlowNodeBase.GetOuter()); + while (IsValid(CurOuter)) + { + AncestorChain.Add(CurOuter); + + CurOuter = Cast(CurOuter->GetOuter()); + } + + FlowArray::ReverseArray(AncestorChain); + + if (bIncludeFromFlowNodeBase) + { + AncestorChain.Add(&FromFlowNodeBase); + } + + return AncestorChain; +} + IFlowOwnerInterface* UFlowNodeBase::TryGetFlowOwnerInterfaceFromRootFlowOwner(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass) { const UClass* RootFlowOwnerClass = RootFlowOwner.GetClass(); @@ -441,7 +523,9 @@ EFlowAddOnAcceptResult UFlowNodeBase::CheckAcceptFlowNodeAddOnChild( } #endif // WITH_EDITOR -EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnConst(const FConstFlowNodeAddOnFunction& Function) const +EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnConst( + const FConstFlowNodeAddOnFunction& Function, + EFlowForEachAddOnChildRule AddOnChildRule) const { FLOW_ASSERT_ENUM_MAX(EFlowForEachAddOnFunctionReturnValue, 3); @@ -461,18 +545,24 @@ EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnConst(const FCon break; } - ReturnValue = AddOn->ForEachAddOnConst(Function); - - if (!ShouldContinueForEach(ReturnValue)) + FLOW_ASSERT_ENUM_MAX(EFlowForEachAddOnChildRule, 2); + if (AddOnChildRule == EFlowForEachAddOnChildRule::AllChildren) { - break; + ReturnValue = AddOn->ForEachAddOnConst(Function); + + if (!ShouldContinueForEach(ReturnValue)) + { + break; + } } } return ReturnValue; } -EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOn(const FFlowNodeAddOnFunction& Function) const +EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOn( + const FFlowNodeAddOnFunction& Function, + EFlowForEachAddOnChildRule AddOnChildRule) const { FLOW_ASSERT_ENUM_MAX(EFlowForEachAddOnFunctionReturnValue, 3); @@ -492,18 +582,25 @@ EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOn(const FFlowNode break; } - ReturnValue = AddOn->ForEachAddOn(Function); - - if (!ShouldContinueForEach(ReturnValue)) + FLOW_ASSERT_ENUM_MAX(EFlowForEachAddOnChildRule, 2); + if (AddOnChildRule == EFlowForEachAddOnChildRule::AllChildren) { - break; + ReturnValue = AddOn->ForEachAddOn(Function); + + if (!ShouldContinueForEach(ReturnValue)) + { + break; + } } } return ReturnValue; } -EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnForClassConst(const UClass& InterfaceOrClass, const FConstFlowNodeAddOnFunction& Function) const +EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnForClassConst( + const UClass& InterfaceOrClass, + const FConstFlowNodeAddOnFunction& Function, + EFlowForEachAddOnChildRule AddOnChildRule) const { FLOW_ASSERT_ENUM_MAX(EFlowForEachAddOnFunctionReturnValue, 3); @@ -516,9 +613,7 @@ EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnForClassConst(co continue; } - // InterfaceOrClass can either be the AddOn's UClass (or its superclass) - // or an interface (the UClass version) that its UClass implements - if (AddOn->IsA(&InterfaceOrClass) || AddOn->GetClass()->ImplementsInterface(&InterfaceOrClass)) + if (AddOn->IsClassOrImplementsInterface(InterfaceOrClass)) { ReturnValue = Function(*AddOn); @@ -528,18 +623,25 @@ EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnForClassConst(co } } - ReturnValue = AddOn->ForEachAddOnForClassConst(InterfaceOrClass, Function); - - if (!ShouldContinueForEach(ReturnValue)) + FLOW_ASSERT_ENUM_MAX(EFlowForEachAddOnChildRule, 2); + if (AddOnChildRule == EFlowForEachAddOnChildRule::AllChildren) { - break; + ReturnValue = AddOn->ForEachAddOnForClassConst(InterfaceOrClass, Function); + + if (!ShouldContinueForEach(ReturnValue)) + { + break; + } } } return ReturnValue; } -EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnForClass(const UClass& InterfaceOrClass, const FFlowNodeAddOnFunction& Function) const +EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnForClass( + const UClass& InterfaceOrClass, + const FFlowNodeAddOnFunction& Function, + EFlowForEachAddOnChildRule AddOnChildRule) const { FLOW_ASSERT_ENUM_MAX(EFlowForEachAddOnFunctionReturnValue, 3); @@ -552,9 +654,7 @@ EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnForClass(const U continue; } - // InterfaceOrClass can either be the AddOn's UClass (or its superclass) - // or an interface (the UClass version) that its UClass implements - if (AddOn->IsA(&InterfaceOrClass) || AddOn->GetClass()->ImplementsInterface(&InterfaceOrClass)) + if (AddOn->IsClassOrImplementsInterface(InterfaceOrClass)) { ReturnValue = Function(*AddOn); @@ -564,11 +664,15 @@ EFlowForEachAddOnFunctionReturnValue UFlowNodeBase::ForEachAddOnForClass(const U } } - ReturnValue = AddOn->ForEachAddOnForClass(InterfaceOrClass, Function); - - if (!ShouldContinueForEach(ReturnValue)) + FLOW_ASSERT_ENUM_MAX(EFlowForEachAddOnChildRule, 2); + if (AddOnChildRule == EFlowForEachAddOnChildRule::AllChildren) { - break; + ReturnValue = AddOn->ForEachAddOnForClass(InterfaceOrClass, Function); + + if (!ShouldContinueForEach(ReturnValue)) + { + break; + } } } @@ -699,7 +803,6 @@ FText UFlowNodeBase::GetNodeToolTip() const } } - return GetClass()->GetToolTipText(); } @@ -807,21 +910,16 @@ void UFlowNodeBase::LogError(FString Message, const EFlowOnScreenMessageType OnS // OnScreen Message if (OnScreenMessageType == EFlowOnScreenMessageType::Permanent) { - if (UWorld* World = GetWorld()) + if (GetWorld()) { - if (UViewportStatsSubsystem* StatsSubsystem = World->GetSubsystem()) + if (UViewportStatsSubsystem* StatsSubsystem = GetWorld()->GetSubsystem()) { - StatsSubsystem->AddDisplayDelegate([WeakThis = TWeakObjectPtr(this), Message](FText& OutText, FLinearColor& OutColor) + StatsSubsystem->AddDisplayDelegate([this, Message](FText& OutText, FLinearColor& OutColor) { - const UFlowNodeBase* ThisPtr = WeakThis.Get(); - if (ThisPtr && ThisPtr->GetFlowNodeSelfOrOwner()->GetActivationState() != EFlowNodeState::NeverActivated) - { - OutText = FText::FromString(Message); - OutColor = FLinearColor::Red; - return true; - } - - return false; + OutText = FText::FromString(Message); + OutColor = FLinearColor::Red; + + return IsValid(this) && GetFlowNodeSelfOrOwner()->GetActivationState() != EFlowNodeState::NeverActivated; }); } } @@ -910,6 +1008,212 @@ bool UFlowNodeBase::BuildMessage(FString& Message) const } #endif +bool UFlowNodeBase::TryAddValueToFormatNamedArguments(const FFlowNamedDataPinProperty& NamedDataPinProperty, FFormatNamedArguments& InOutArguments) const +{ + const FFlowDataPinProperty* FlowDataPinProperty = NamedDataPinProperty.DataPinProperty.GetPtr(); + if (!FlowDataPinProperty) + { + return false; + } + + const EFlowPinType FlowPinType = FlowDataPinProperty->GetFlowPinType(); + + FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); + switch (FlowPinType) + { + case EFlowPinType::Exec: + { + LogError(TEXT("Cannot add Exec pin value to FFormatNamedArguments")); + } + break; + + case EFlowPinType::InstancedStruct: + { + LogError(TEXT("Cannot add InstancedStruct pin value to FFormatNamedArguments")); + } + break; + + case EFlowPinType::Bool: + { + const FFlowDataPinResult_Bool ResolvedResult = TryResolveDataPinAsBool(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(ResolvedResult.Value)); + + return true; + } + } + break; + + case EFlowPinType::Int: + { + const FFlowDataPinResult_Int ResolvedResult = TryResolveDataPinAsInt(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(ResolvedResult.Value)); + + return true; + } + } + break; + + case EFlowPinType::Float: + { + const FFlowDataPinResult_Float ResolvedResult = TryResolveDataPinAsFloat(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(ResolvedResult.Value)); + + return true; + } + } + break; + + case EFlowPinType::Name: + { + const FFlowDataPinResult_Name ResolvedResult = TryResolveDataPinAsName(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); + + return true; + } + } + break; + + case EFlowPinType::String: + { + const FFlowDataPinResult_String ResolvedResult = TryResolveDataPinAsString(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value))); + + return true; + } + } + break; + + case EFlowPinType::Text: + { + const FFlowDataPinResult_Text ResolvedResult = TryResolveDataPinAsText(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(ResolvedResult.Value)); + + return true; + } + } + break; + + case EFlowPinType::Enum: + { + const FFlowDataPinResult_Enum ResolvedResult = TryResolveDataPinAsEnum(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); + + return true; + } + } + break; + + case EFlowPinType::Vector: + { + const FFlowDataPinResult_Vector ResolvedResult = TryResolveDataPinAsVector(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); + + return true; + } + } + break; + + case EFlowPinType::Rotator: + { + const FFlowDataPinResult_Rotator ResolvedResult = TryResolveDataPinAsRotator(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); + + return true; + } + } + break; + + case EFlowPinType::Transform: + { + const FFlowDataPinResult_Transform ResolvedResult = TryResolveDataPinAsTransform(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); + + return true; + } + } + break; + + case EFlowPinType::GameplayTag: + { + const FFlowDataPinResult_GameplayTag ResolvedResult = TryResolveDataPinAsGameplayTag(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); + + return true; + } + } + break; + + case EFlowPinType::GameplayTagContainer: + { + const FFlowDataPinResult_GameplayTagContainer ResolvedResult = TryResolveDataPinAsGameplayTagContainer(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); + + return true; + } + } + break; + + case EFlowPinType::Object: + { + const FFlowDataPinResult_Object ResolvedResult = TryResolveDataPinAsObject(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + if (IsValid(ResolvedResult.Value)) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value->GetName()))); + } + else + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(TEXT("null")))); + } + + return true; + } + } + break; + + case EFlowPinType::Class: + { + const FFlowDataPinResult_Class ResolvedResult = TryResolveDataPinAsClass(NamedDataPinProperty.Name); + if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.GetAsSoftClass().ToString()))); + + return true; + } + } + break; + + default: break; + } + + return false; +} + EFlowDataPinResolveResult UFlowNodeBase::TryResolveDataPinPrerequisites(const FName& PinName, const UFlowNode*& FlowNode, const FFlowPin*& FlowPin, EFlowPinType PinType) const { FlowNode = GetFlowNodeSelfOrOwner(); diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_CustomEventBase.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_CustomEventBase.cpp index 4ca81b5ca..1beafac65 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_CustomEventBase.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_CustomEventBase.cpp @@ -2,6 +2,9 @@ #include "Nodes/Graph/FlowNode_CustomEventBase.h" #include "FlowSettings.h" +#if WITH_EDITOR +#include "Misc/DataValidation.h" +#endif #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_CustomEventBase) @@ -25,7 +28,7 @@ void UFlowNode_CustomEventBase::SetEventName(const FName& InEventName) #if WITH_EDITOR // Must reconstruct the visual representation if anything that is included in AdaptiveNodeTitles changes OnReconstructionRequested.ExecuteIfBound(); -#endif // WITH_EDITOR +#endif } } @@ -41,14 +44,19 @@ FString UFlowNode_CustomEventBase::GetNodeDescription() const return EventName.ToString(); } -EDataValidationResult UFlowNode_CustomEventBase::ValidateNode() +EDataValidationResult UFlowNode_CustomEventBase::ValidateNode(FDataValidationContext& Context) const { + const EDataValidationResult SuperResult = Super::ValidateNode(Context); + + EDataValidationResult FinalResult = CombineDataValidationResults(SuperResult, EDataValidationResult::Valid); + if (EventName.IsNone()) { - ValidationLog.Error(TEXT("Event Name is empty!"), this); - return EDataValidationResult::Invalid; + Context.AddError(FText::FromString(TEXT("Event Name is empty!"))); + + FinalResult = CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } - return EDataValidationResult::Valid; + return FinalResult; } #endif diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_DefineProperties.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_DefineProperties.cpp index dc55e7a43..fd835b546 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_DefineProperties.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_DefineProperties.cpp @@ -8,7 +8,7 @@ UFlowNode_DefineProperties::UFlowNode_DefineProperties(const FObjectInitializer& : Super(ObjectInitializer) { #if WITH_EDITOR - NodeDisplayStyle = FlowNodeStyle::InOut; + NodeDisplayStyle = FlowNodeStyle::Terminal; Category = TEXT("Graph"); #endif @@ -26,7 +26,7 @@ bool UFlowNode_DefineProperties::TryFindPropertyByRemappedPinName( { // The start node stores its properties in instanced structs in an array, so look there first - for (const FFlowNamedDataPinOutputProperty& NamedProperty : OutputProperties) + for (const FFlowNamedDataPinProperty& NamedProperty : NamedProperties) { if (NamedProperty.Name == RemappedPinName && NamedProperty.IsValid()) { @@ -36,19 +36,30 @@ bool UFlowNode_DefineProperties::TryFindPropertyByRemappedPinName( } } - return Super::TryFindPropertyByPinName(RemappedPinName, OutFoundProperty, OutFoundInstancedStruct, InOutResult); + return Super::TryFindPropertyByRemappedPinName(RemappedPinName, OutFoundProperty, OutFoundInstancedStruct, InOutResult); } #if WITH_EDITOR void UFlowNode_DefineProperties::AutoGenerateDataPins(TMap& PinNameToBoundPropertyMap, TArray& InputDataPins, TArray& OutputDataPins) const { - for (const FFlowNamedDataPinOutputProperty& DataPinProperty : OutputProperties) + for (const FFlowNamedDataPinProperty& DataPinProperty : NamedProperties) { if (DataPinProperty.IsValid()) { PinNameToBoundPropertyMap.Add(DataPinProperty.Name, DataPinProperty.Name); - OutputDataPins.AddUnique(DataPinProperty.CreateFlowPin()); + if (DataPinProperty.IsInputProperty()) + { + InputDataPins.AddUnique(DataPinProperty.CreateFlowPin()); + } + else if (DataPinProperty.IsOutputProperty()) + { + OutputDataPins.AddUnique(DataPinProperty.CreateFlowPin()); + } + else + { + LogError(TEXT("DataPin must be either an Input or Output property!")); + } } } } @@ -69,18 +80,18 @@ void UFlowNode_DefineProperties::PostEditChangeChainProperty(FPropertyChangedCha if (PropertyChainEvent.ChangeType == EPropertyChangeType::ValueSet && Property->GetFName() == GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Enum, EnumName)) { - for (FFlowNamedDataPinOutputProperty& OutputProperty : OutputProperties) + for (FFlowNamedDataPinProperty& NamedProperty : NamedProperties) { - if (!OutputProperty.IsValid()) + if (!NamedProperty.IsValid()) { continue; } - const FFlowDataPinProperty& FlowDataPinProperty = OutputProperty.DataPinProperty.Get(); + const FFlowDataPinProperty& FlowDataPinProperty = NamedProperty.DataPinProperty.Get(); if (FlowDataPinProperty.GetFlowPinType() == EFlowPinType::Enum) { - FFlowDataPinOutputProperty_Enum& EnumProperty = OutputProperty.DataPinProperty.GetMutable(); + FFlowDataPinOutputProperty_Enum& EnumProperty = NamedProperty.DataPinProperty.GetMutable(); EnumProperty.OnEnumNameChanged(); } @@ -100,10 +111,35 @@ void UFlowNode_DefineProperties::PostEditChangeChainProperty(FPropertyChangedCha const uint32 PropertyChangedTypeFlags = (PropertyChainEvent.ChangeType & RelevantChangeTypesForReconstructionMask); const bool bIsRelevantChangeTypeForReconstruction = PropertyChangedTypeFlags != 0; - const bool bChangedOutputProperties = Property->GetFName() == GET_MEMBER_NAME_CHECKED(UFlowNode_DefineProperties, OutputProperties); + const bool bChangedOutputProperties = Property->GetFName() == GET_MEMBER_NAME_CHECKED(UFlowNode_DefineProperties, NamedProperties); if (bIsRelevantChangeTypeForReconstruction && bChangedOutputProperties) { OnReconstructionRequested.ExecuteIfBound(); } } #endif // WITH_EDITOR + +bool UFlowNode_DefineProperties::TryFormatTextWithNamedPropertiesAsParameters(const FText& FormatText, FText& OutFormattedText) const +{ + if (NamedProperties.IsEmpty()) + { + return false; + } + + FFormatNamedArguments Arguments; + for (const FFlowNamedDataPinProperty& NamedProperty : NamedProperties) + { + if (!NamedProperty.Name.IsValid()) + { + LogWarning(TEXT("Could not format text with a nameless named property")); + } + else if (!TryAddValueToFormatNamedArguments(NamedProperty, Arguments)) + { + LogWarning(FString::Printf(TEXT("Could not format text for named property %s"), *NamedProperty.Name.ToString())); + } + } + + OutFormattedText = FText::Format(FormatText, Arguments); + + return true; +} diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp new file mode 100644 index 000000000..83adf38c0 --- /dev/null +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp @@ -0,0 +1,115 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Nodes/Graph/FlowNode_FormatText.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_FormatText) + +#define LOCTEXT_NAMESPACE "FlowNode_FormatText" + +const FName UFlowNode_FormatText::OUTPIN_TextOutput("Formatted Text"); + +UFlowNode_FormatText::UFlowNode_FormatText(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITOR + Category = TEXT("Graph"); + NodeDisplayStyle = FlowNodeStyle::Terminal; +#endif + + OutputPins.Add(FFlowPin(OUTPIN_TextOutput, EFlowPinType::Text)); +} + +FFlowDataPinResult_Name UFlowNode_FormatText::TrySupplyDataPinAsName_Implementation(const FName& PinName) const +{ + FText FormattedText; + const EFlowDataPinResolveResult FormatResult = TryResolveFormatText(PinName, FormattedText); + if (FormatResult != EFlowDataPinResolveResult::Invalid) + { + if (FormatResult == EFlowDataPinResolveResult::Success) + { + return FFlowDataPinResult_Name(FName(FormattedText.ToString())); + } + else + { + return FFlowDataPinResult_Name(FormatResult); + } + } + + return Super::TrySupplyDataPinAsName_Implementation(PinName); +} + +FFlowDataPinResult_String UFlowNode_FormatText::TrySupplyDataPinAsString_Implementation(const FName& PinName) const +{ + FText FormattedText; + const EFlowDataPinResolveResult FormatResult = TryResolveFormatText(PinName, FormattedText); + if (FormatResult != EFlowDataPinResolveResult::Invalid) + { + if (FormatResult == EFlowDataPinResolveResult::Success) + { + return FFlowDataPinResult_String(FormattedText.ToString()); + } + else + { + return FFlowDataPinResult_String(FormatResult); + } + } + + return Super::TrySupplyDataPinAsString_Implementation(PinName); +} + +FFlowDataPinResult_Text UFlowNode_FormatText::TrySupplyDataPinAsText_Implementation(const FName& PinName) const +{ + FText FormattedText; + const EFlowDataPinResolveResult FormatResult = TryResolveFormatText(PinName, FormattedText); + if (FormatResult != EFlowDataPinResolveResult::Invalid) + { + if (FormatResult == EFlowDataPinResolveResult::Success) + { + return FFlowDataPinResult_Text(FormattedText); + } + else + { + return FFlowDataPinResult_Text(FormatResult); + } + } + + return Super::TrySupplyDataPinAsText_Implementation(PinName); +} + +EFlowDataPinResolveResult UFlowNode_FormatText::TryResolveFormatText(const FName& PinName, FText& OutFormattedText) const +{ + if (PinName == OUTPIN_TextOutput) + { + if (TryFormatTextWithNamedPropertiesAsParameters(FormatText, OutFormattedText)) + { + return EFlowDataPinResolveResult::Success; + } + else + { + return EFlowDataPinResolveResult::FailedWithError; + } + } + + return EFlowDataPinResolveResult::Invalid; +} + +#if WITH_EDITOR + +void UFlowNode_FormatText::UpdateNodeConfigText_Implementation() +{ + constexpr bool bErrorIfInputPinNotFound = false; + const bool bIsInputConnected = IsInputConnected(GET_MEMBER_NAME_CHECKED(ThisClass, FormatText), bErrorIfInputPinNotFound); + + if (bIsInputConnected) + { + SetNodeConfigText(FText()); + } + else + { + SetNodeConfigText(FormatText); + } +} + +#endif + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_Start.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_Start.cpp index a75f1fbda..3e30befa8 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_Start.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_Start.cpp @@ -31,7 +31,7 @@ void UFlowNode_Start::SetDataPinValueSupplier(IFlowDataPinValueSupplierInterface bool UFlowNode_Start::TryAppendExternalInputPins(TArray& InOutPins) const { // Add pins for all of the Flow DataPin Properties - for (const FFlowNamedDataPinOutputProperty& DataPinProperty : OutputProperties) + for (const FFlowNamedDataPinProperty& DataPinProperty : NamedProperties) { if (DataPinProperty.IsValid()) { @@ -39,7 +39,7 @@ bool UFlowNode_Start::TryAppendExternalInputPins(TArray& InOutPins) co } } - return !OutputProperties.IsEmpty(); + return !NamedProperties.IsEmpty(); } #endif // WITH_EDITOR diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp index 98d42b3e2..64aa127b7 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp @@ -6,6 +6,9 @@ #include "FlowSettings.h" #include "FlowSubsystem.h" #include "Interfaces/FlowNodeWithExternalDataPinSupplierInterface.h" +#if WITH_EDITOR +#include "Misc/DataValidation.h" +#endif // WITH_EDITOR #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_SubGraph) @@ -131,15 +134,20 @@ UObject* UFlowNode_SubGraph::GetAssetToEdit() return Asset.IsNull() ? nullptr : Asset.LoadSynchronous(); } -EDataValidationResult UFlowNode_SubGraph::ValidateNode() +EDataValidationResult UFlowNode_SubGraph::ValidateNode(FDataValidationContext& Context) const { + const EDataValidationResult SuperResult = Super::ValidateNode(Context); + + EDataValidationResult FinalResult = CombineDataValidationResults(SuperResult, EDataValidationResult::Valid); + if (Asset.IsNull()) { - ValidationLog.Error(TEXT("Flow Asset not assigned or invalid!"), this); - return EDataValidationResult::Invalid; + Context.AddError(FText::FromString(TEXT("Flow Asset not assigned or invalid!"))); + + FinalResult = CombineDataValidationResults(FinalResult, EDataValidationResult::Invalid); } - return EDataValidationResult::Valid; + return FinalResult; } TArray UFlowNode_SubGraph::GetContextInputs() const diff --git a/Source/Flow/Private/Types/FlowDataPinProperties.cpp b/Source/Flow/Private/Types/FlowDataPinProperties.cpp index c640938c7..1f11e190a 100644 --- a/Source/Flow/Private/Types/FlowDataPinProperties.cpp +++ b/Source/Flow/Private/Types/FlowDataPinProperties.cpp @@ -79,7 +79,7 @@ void FFlowDataPinOutputProperty_Enum::OnEnumNameChanged() } } -FText FFlowNamedDataPinOutputProperty::BuildHeaderText() const +FText FFlowNamedDataPinProperty::BuildHeaderText() const { EFlowPinType PinType = EFlowPinType::Invalid; @@ -88,7 +88,7 @@ FText FFlowNamedDataPinOutputProperty::BuildHeaderText() const PinType = DataPinPropertyPtr->GetFlowPinType(); } - return FText::Format(LOCTEXT("FlowNamedDataPinOutputPropertyHeader", "{0} ({1})"), { FText::FromName(Name), UEnum::GetDisplayValueAsText(PinType) }); + return FText::Format(LOCTEXT("FlowNamedDataPinPropertyHeader", "{0} ({1})"), { FText::FromName(Name), UEnum::GetDisplayValueAsText(PinType) }); } UClass* FFlowDataPinOutputProperty_Class::DeriveMetaClass(const FProperty& MetaDataProperty) const @@ -167,6 +167,26 @@ UClass* FFlowDataPinOutputProperty_Object::TryGetObjectClassFromProperty(const F } #endif +bool FFlowNamedDataPinProperty::IsInputProperty() const +{ + if (const FFlowDataPinProperty* DataPinPropertyPtr = DataPinProperty.GetPtr()) + { + return DataPinPropertyPtr->IsInputProperty(); + } + + return false; +} + +bool FFlowNamedDataPinProperty::IsOutputProperty() const +{ + if (const FFlowDataPinProperty* DataPinPropertyPtr = DataPinProperty.GetPtr()) + { + return !DataPinPropertyPtr->IsInputProperty(); + } + + return false; +} + FFlowDataPinOutputProperty_Object::FFlowDataPinOutputProperty_Object(UObject* InValue, UClass* InClassFilter) : Super() #if WITH_EDITOR diff --git a/Source/Flow/Private/Types/FlowInjectComponentsHelper.cpp b/Source/Flow/Private/Types/FlowInjectComponentsHelper.cpp index 21a9853fa..59e7259ff 100644 --- a/Source/Flow/Private/Types/FlowInjectComponentsHelper.cpp +++ b/Source/Flow/Private/Types/FlowInjectComponentsHelper.cpp @@ -59,7 +59,7 @@ UActorComponent* FFlowInjectComponentsHelper::TryCreateComponentInstanceForActor { const EObjectFlags InstanceFlags = ComponentTemplate.GetFlags() | RF_Transient; - UActorComponent* ComponentInstance = NewObject(&Actor, ComponentTemplate.GetFName(), InstanceFlags, &ComponentTemplate); + UActorComponent* ComponentInstance = NewObject(&Actor, ComponentTemplate.GetClass(), ComponentTemplate.GetFName(), InstanceFlags, &ComponentTemplate); return ComponentInstance; } diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn.h b/Source/Flow/Public/AddOns/FlowNodeAddOn.h index e066a2c4f..30a9b63d0 100644 --- a/Source/Flow/Public/AddOns/FlowNodeAddOn.h +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn.h @@ -64,16 +64,28 @@ class UFlowNodeAddOn : public UFlowNodeBase // -- // UFlowNodeAddOn + + //// The FlowNode that contains this AddOn + // (accessible only when initialized, runtime only) UFUNCTION(BlueprintCallable, BlueprintPure, Category = "FlowNodeAddon", DisplayName = "Get Flow Node") FLOW_API UFlowNode* GetFlowNode() const; + + // Will crawl the hierarchy until it finds a flow node (addons can be attached to other add-ons). + FLOW_API UFlowNode* FindOwningFlowNode() const; // -- + // Returns a random seed suitable for this flow node addon + // by default, uses the seed for the Flow Node that this addon is attached to. + FLOW_API virtual int32 GetRandomSeed() const override; + #if WITH_EDITOR // IFlowContextPinSupplierInterface FLOW_API virtual bool SupportsContextPins() const override { return Super::SupportsContextPins() || (!InputPins.IsEmpty() || !OutputPins.IsEmpty()); } FLOW_API virtual TArray GetContextInputs() const override; FLOW_API virtual TArray GetContextOutputs() const override; // -- + + FLOW_API void RequestReconstructionOnOwningFlowNode() const; #endif // WITH_EDITOR protected: diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateAND.h b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateAND.h index 381fbb632..c04202ebe 100644 --- a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateAND.h +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateAND.h @@ -28,5 +28,5 @@ class UFlowNodeAddOn_PredicateAND virtual bool EvaluatePredicate_Implementation() const override; // -- - static bool EvaluatePredicateAND(const TArray& AddOns); + FLOW_API static bool EvaluatePredicateAND(const TArray& AddOns); }; diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateOR.h b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateOR.h index 0df21db22..2748506a4 100644 --- a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateOR.h +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateOR.h @@ -28,5 +28,5 @@ class UFlowNodeAddOn_PredicateOR virtual bool EvaluatePredicate_Implementation() const override; // -- - static bool EvaluatePredicateOR(const TArray& AddOns); + FLOW_API static bool EvaluatePredicateOR(const TArray& AddOns); }; diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index 0e2963463..585fa6703 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -124,7 +124,7 @@ class FLOW_API UFlowAsset : public UObject bool CanFlowAssetUseFlowNodeClass(const UClass& FlowNodeClass) const; bool CanFlowAssetReferenceFlowNode(const UClass& FlowNodeClass, FText* OutOptionalFailureReason = nullptr) const; - bool IsFlowNodeClassInAllowedClasses(const UClass& FlowNodeClass, const TSubclassOf& RequiredAncestor = nullptr) const; + bool IsFlowNodeClassInAllowedClasses(const UClass& FlowNodeClass, const TSubclassOf RequiredAncestor = nullptr) const; bool IsFlowNodeClassInDeniedClasses(const UClass& FlowNodeClass) const; #endif @@ -213,11 +213,14 @@ class FLOW_API UFlowAsset : public UObject UFUNCTION(BlueprintPure, Category = "FlowAsset") virtual UFlowNode* GetDefaultEntryNode() const; + // Gathers all of the nodes that are connected to the Start & Custom Inputs of the flow graph + TArray GatherNodesConnectedToAllInputs() const; + UFUNCTION(BlueprintPure, Category = "FlowAsset", meta = (DeterminesOutputType = "FlowNodeClass")) TArray GetNodesInExecutionOrder(UFlowNode* FirstIteratedNode, const TSubclassOf FlowNodeClass); template - void GetNodesInExecutionOrder(UFlowNode* FirstIteratedNode, TArray& OutNodes) + void GetNodesInExecutionOrder(UFlowNode* FirstIteratedNode, TArray& OutNodes) const { static_assert(TPointerIsConvertibleFromTo::Value, "'T' template parameter to GetNodesInExecutionOrder must be derived from UFlowNode"); @@ -230,7 +233,7 @@ class FLOW_API UFlowAsset : public UObject protected: template - void GetNodesInExecutionOrder_Recursive(UFlowNode* Node, TSet>& IteratedNodes, TArray& OutNodes) + void GetNodesInExecutionOrder_Recursive(UFlowNode* Node, TSet>& IteratedNodes, TArray& OutNodes) const { IteratedNodes.Add(Node); diff --git a/Source/Flow/Public/FlowComponent.h b/Source/Flow/Public/FlowComponent.h index d579e2568..cf2d00b13 100644 --- a/Source/Flow/Public/FlowComponent.h +++ b/Source/Flow/Public/FlowComponent.h @@ -53,9 +53,18 @@ class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterfa ////////////////////////////////////////////////////////////////////////// // Identity Tags - UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_IdentityTags, Category = "Flow") + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Flow") FGameplayTagContainer IdentityTags; +private: + // Used to replicate tags added during gameplay + UPROPERTY(ReplicatedUsing = OnRep_AddedIdentityTags) + FGameplayTagContainer AddedIdentityTags; + + // Used to replicate tags removed during gameplay + UPROPERTY(ReplicatedUsing = OnRep_RemovedIdentityTags) + FGameplayTagContainer RemovedIdentityTags; + public: virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; @@ -79,7 +88,10 @@ class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterfa private: UFUNCTION() - void OnRep_IdentityTags(const FGameplayTagContainer& PreviousTags); + void OnRep_AddedIdentityTags(); + + UFUNCTION() + void OnRep_RemovedIdentityTags(); public: UPROPERTY(BlueprintAssignable, Category = "Flow") diff --git a/Source/Flow/Public/FlowTypes.h b/Source/Flow/Public/FlowTypes.h index d53b4b79a..301615181 100644 --- a/Source/Flow/Public/FlowTypes.h +++ b/Source/Flow/Public/FlowTypes.h @@ -38,9 +38,18 @@ enum class EFlowNodeState : uint8 Max UMETA(Hidden), Invalid UMETA(Hidden), Min = 0 UMETA(Hidden), + + // State subrange for states that count as "Finished" + FinishedFirst = Completed UMETA(Hidden), + FinishedLast = Aborted UMETA(Hidden), }; FLOW_ENUM_RANGE_VALUES(EFlowNodeState) +namespace EFlowNodeState_Classifiers +{ + FORCEINLINE bool IsFinishedState(EFlowNodeState State) { return FLOW_IS_ENUM_IN_SUBRANGE(State, EFlowNodeState::Finished); } +} + // Finish Policy value is read by Flow Node // Nodes have opportunity to terminate themselves differently if Flow Graph has been aborted // Example: Spawn node might despawn all actors if Flow Graph is aborted, not completed @@ -160,3 +169,18 @@ namespace EFlowForEachAddOnFunctionReturnValue_Classifiers { FORCEINLINE bool ShouldContinueForEach(EFlowForEachAddOnFunctionReturnValue Result) { return FLOW_IS_ENUM_IN_SUBRANGE(Result, EFlowForEachAddOnFunctionReturnValue::ContinueForEach); } } + +UENUM() +enum class EFlowForEachAddOnChildRule : int8 +{ + // Apply the Function to all child addons (and children of addons, etc.) + AllChildren, + + // Apply the Function to immediate child addons only (do not apply to their children) + ImmediateChildrenOnly, + + Max UMETA(Hidden), + Invalid = -1 UMETA(Hidden), + Min = 0 UMETA(Hidden), +}; +FLOW_ENUM_RANGE_VALUES(EFlowForEachAddOnChildRule); diff --git a/Source/Flow/Public/Interfaces/FlowDataPinPropertyProviderInterface.h b/Source/Flow/Public/Interfaces/FlowDataPinPropertyProviderInterface.h index f5225da2a..63789d62d 100644 --- a/Source/Flow/Public/Interfaces/FlowDataPinPropertyProviderInterface.h +++ b/Source/Flow/Public/Interfaces/FlowDataPinPropertyProviderInterface.h @@ -2,10 +2,12 @@ #pragma once -#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 5 -#include "InstancedStruct.h" -#else +#include "Runtime/Launch/Resources/Version.h" + +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 4 #include "StructUtils/InstancedStruct.h" +#else +#include "InstancedStruct.h" #endif #include "UObject/Interface.h" diff --git a/Source/Flow/Public/Nodes/Actor/FlowNode_ComponentObserver.h b/Source/Flow/Public/Nodes/Actor/FlowNode_ComponentObserver.h index ecc5bbf35..b0e195acc 100644 --- a/Source/Flow/Public/Nodes/Actor/FlowNode_ComponentObserver.h +++ b/Source/Flow/Public/Nodes/Actor/FlowNode_ComponentObserver.h @@ -70,7 +70,7 @@ class FLOW_API UFlowNode_ComponentObserver : public UFlowNode #if WITH_EDITOR public: virtual FString GetNodeDescription() const override; - virtual EDataValidationResult ValidateNode() override; + virtual EDataValidationResult ValidateNode(FDataValidationContext& Context) const override; virtual FString GetStatusString() const override; #endif diff --git a/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h b/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h index 6b34ac0c1..728876aa2 100644 --- a/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h +++ b/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h @@ -64,7 +64,32 @@ class FLOW_API UFlowNode_ExecuteComponent : public UFlowNode virtual void UpdateNodeConfigText_Implementation() override; // -- + // IFlowDataPinValueSupplierInterface + virtual bool CanSupplyDataPinValues_Implementation() const override; + virtual FFlowDataPinResult_Bool TrySupplyDataPinAsBool_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Int TrySupplyDataPinAsInt_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Float TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Name TrySupplyDataPinAsName_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_String TrySupplyDataPinAsString_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Text TrySupplyDataPinAsText_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Enum TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Vector TrySupplyDataPinAsVector_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Rotator TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Transform TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_GameplayTag TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_GameplayTagContainer TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_InstancedStruct TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Object TrySupplyDataPinAsObject_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Class TrySupplyDataPinAsClass_Implementation(const FName& PinName) const override; + // -- + #if WITH_EDITOR + // IFlowContextPinSupplierInterface + virtual bool SupportsContextPins() const override { return true; } + virtual TArray GetContextInputs() const override; + virtual TArray GetContextOutputs() const override; + // -- + // UObject virtual void PostLoad() override; virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; @@ -72,7 +97,7 @@ class FLOW_API UFlowNode_ExecuteComponent : public UFlowNode // UFlowNode virtual FText GetNodeTitle() const override; - virtual EDataValidationResult ValidateNode() override; + virtual EDataValidationResult ValidateNode(FDataValidationContext& Context) const override; virtual FString GetStatusString() const override; // -- @@ -90,6 +115,7 @@ class FLOW_API UFlowNode_ExecuteComponent : public UFlowNode bool TryInjectComponent(); UActorComponent* TryResolveComponent(); + UActorComponent* GetResolvedComponent() const; TSubclassOf TryGetExpectedActorOwnerClass() const; protected: diff --git a/Source/Flow/Public/Nodes/Actor/FlowNode_NotifyActor.h b/Source/Flow/Public/Nodes/Actor/FlowNode_NotifyActor.h index aa250b4bd..19675a577 100644 --- a/Source/Flow/Public/Nodes/Actor/FlowNode_NotifyActor.h +++ b/Source/Flow/Public/Nodes/Actor/FlowNode_NotifyActor.h @@ -40,6 +40,6 @@ class FLOW_API UFlowNode_NotifyActor : public UFlowNode #if WITH_EDITOR public: virtual FString GetNodeDescription() const override; - virtual EDataValidationResult ValidateNode() override; + virtual EDataValidationResult ValidateNode(FDataValidationContext& Context) const override; #endif }; diff --git a/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h b/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h index c878781ff..d45fda008 100644 --- a/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h +++ b/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h @@ -126,7 +126,7 @@ class FLOW_API UFlowNode_PlayLevelSequence : public UFlowNode #if WITH_EDITOR virtual FString GetNodeDescription() const override; - virtual EDataValidationResult ValidateNode() override; + virtual EDataValidationResult ValidateNode(FDataValidationContext& Context) const override; virtual FString GetStatusString() const override; virtual UObject* GetAssetToEdit() override; diff --git a/Source/Flow/Public/Nodes/Developer/FlowNode_Log.h b/Source/Flow/Public/Nodes/Developer/FlowNode_Log.h index 9f2df3120..0a88727ed 100644 --- a/Source/Flow/Public/Nodes/Developer/FlowNode_Log.h +++ b/Source/Flow/Public/Nodes/Developer/FlowNode_Log.h @@ -2,7 +2,7 @@ #pragma once -#include "Nodes/FlowNode.h" +#include "Nodes/Graph/FlowNode_DefineProperties.h" #include "FlowNode_Log.generated.h" // Variant of ELogVerbosity @@ -22,7 +22,7 @@ enum class EFlowLogVerbosity : uint8 * Optionally shows message on screen */ UCLASS(NotBlueprintable, meta = (DisplayName = "Log", Keywords = "print")) -class FLOW_API UFlowNode_Log : public UFlowNode +class FLOW_API UFlowNode_Log : public UFlowNode_DefineProperties { GENERATED_UCLASS_BODY() @@ -45,7 +45,9 @@ class FLOW_API UFlowNode_Log : public UFlowNode FColor TextColor; protected: + // IFlowCoreExecutableInterface virtual void ExecuteInput(const FName& PinName) override; + // -- #if WITH_EDITOR public: diff --git a/Source/Flow/Public/Nodes/FlowNode.h b/Source/Flow/Public/Nodes/FlowNode.h index 0857485cb..5aca6af8e 100644 --- a/Source/Flow/Public/Nodes/FlowNode.h +++ b/Source/Flow/Public/Nodes/FlowNode.h @@ -59,8 +59,8 @@ class FLOW_API UFlowNode virtual void PostLoad() override; // -- - virtual EDataValidationResult ValidateNode() { return EDataValidationResult::NotValidated; } - + // DEPRECATED - use UFlowNodeBase::ValidateNode(FDataValidationContext& Context) instead + virtual EDataValidationResult DEPRECATED_ValidateNode() { return EDataValidationResult::NotValidated; } #endif // Inherits Guid after graph node @@ -74,6 +74,11 @@ class FLOW_API UFlowNode UFUNCTION(BlueprintPure, Category = "FlowNode") const FGuid& GetGuid() const { return NodeGuid; } + // Returns a random seed suitable for this flow node, + // by default based on the node Guid, + // but may be overridden in subclasses to supply some other value. + virtual int32 GetRandomSeed() const override { return GetTypeHash(NodeGuid); } + public: virtual bool CanFinishGraph() const { return false; } @@ -315,6 +320,7 @@ class FLOW_API UFlowNode public: EFlowNodeState GetActivationState() const { return ActivationState; } + bool HasFinished() const { return EFlowNodeState_Classifiers::IsFinishedState(ActivationState); } #if !UE_BUILD_SHIPPING @@ -335,9 +341,9 @@ class FLOW_API UFlowNode protected: void Deactivate(); +public: virtual void TriggerFirstOutput(const bool bFinish) override; virtual void TriggerOutput(FName PinName, const bool bFinish = false, const EFlowPinActivationType ActivationType = EFlowPinActivationType::Default) override; -public: virtual void Finish() override; private: diff --git a/Source/Flow/Public/Nodes/FlowNodeBase.h b/Source/Flow/Public/Nodes/FlowNodeBase.h index a8578305b..e1c56ef84 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -13,6 +13,7 @@ #include "FlowNodeBase.generated.h" +class FDataValidationContext; class UFlowAsset; class UFlowNode; class UFlowNodeAddOn; @@ -21,6 +22,7 @@ class UEdGraphNode; class IFlowOwnerInterface; class IFlowDataPinValueSupplierInterface; struct FFlowPin; +struct FFlowNamedDataPinProperty; #if WITH_EDITORONLY_DATA DECLARE_DELEGATE(FFlowNodeEvent); @@ -113,6 +115,10 @@ class FLOW_API UFlowNodeBase UFUNCTION(BlueprintCallable, Category = "FlowNode", meta = (HidePin = "ActivationType")) virtual void TriggerOutputPin(const FFlowOutputPinHandle Pin, const bool bFinish = false, const EFlowPinActivationType ActivationType = EFlowPinActivationType::Default); + // Returns a random seed suitable for this flow node base + UFUNCTION(BlueprintPure, Category = "FlowNode") + virtual int32 GetRandomSeed() const PURE_VIRTUAL(GetRandomSeed, return 0;); + ////////////////////////////////////////////////////////////////////////// // Pins @@ -156,6 +162,8 @@ class FLOW_API UFlowNodeBase // NOTE - will consider a UActorComponent owner's owning actor if appropriate IFlowOwnerInterface* GetFlowOwnerInterface() const; + static TArray BuildFlowNodeBaseAncestorChain(UFlowNodeBase& FromFlowNodeBase, bool bIncludeFromFlowNodeBase); + protected: // Helper functions for GetFlowOwnerInterface() static IFlowOwnerInterface* TryGetFlowOwnerInterfaceFromRootFlowOwner(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass); @@ -188,25 +196,38 @@ class FLOW_API UFlowNodeBase EFlowAddOnAcceptResult CheckAcceptFlowNodeAddOnChild(const UFlowNodeAddOn* AddOnTemplate, const TArray& AdditionalAddOnsToAssumeAreChildren) const; #endif // WITH_EDITOR - // Call a function for all of this object's AddOns (recursively iterating AddOns inside AddOn) - EFlowForEachAddOnFunctionReturnValue ForEachAddOnConst(const FConstFlowNodeAddOnFunction& Function) const; - EFlowForEachAddOnFunctionReturnValue ForEachAddOn(const FFlowNodeAddOnFunction& Function) const; + bool IsClassOrImplementsInterface(const UClass& InterfaceOrClass) const + { + // InterfaceOrClass can either be the AddOn's UClass (or its superclass) + // or an interface (the UClass version) that its UClass implements + return IsA(&InterfaceOrClass) || GetClass()->ImplementsInterface(&InterfaceOrClass); + } template + bool IsClassOrImplementsInterface() const + { + return IsClassOrImplementsInterface(*TInterfaceOrClass::StaticClass()); + } + + // Call a function for all of this object's AddOns (recursively iterating AddOns inside AddOn) + EFlowForEachAddOnFunctionReturnValue ForEachAddOnConst(const FConstFlowNodeAddOnFunction& Function, EFlowForEachAddOnChildRule AddOnChildRule = EFlowForEachAddOnChildRule::AllChildren) const; + EFlowForEachAddOnFunctionReturnValue ForEachAddOn(const FFlowNodeAddOnFunction& Function, EFlowForEachAddOnChildRule AddOnChildRule = EFlowForEachAddOnChildRule::AllChildren) const; + + template EFlowForEachAddOnFunctionReturnValue ForEachAddOnForClassConst(const FConstFlowNodeAddOnFunction Function) const { - return ForEachAddOnForClassConst(*TInterfaceOrClass::StaticClass(), Function); + return ForEachAddOnForClassConst(*TInterfaceOrClass::StaticClass(), Function, TAddOnChildRule); } - EFlowForEachAddOnFunctionReturnValue ForEachAddOnForClassConst(const UClass& InterfaceOrClass, const FConstFlowNodeAddOnFunction& Function) const; + EFlowForEachAddOnFunctionReturnValue ForEachAddOnForClassConst(const UClass& InterfaceOrClass, const FConstFlowNodeAddOnFunction& Function, EFlowForEachAddOnChildRule AddOnChildRule = EFlowForEachAddOnChildRule::AllChildren) const; - template + template EFlowForEachAddOnFunctionReturnValue ForEachAddOnForClass(const FFlowNodeAddOnFunction Function) const { - return ForEachAddOnForClass(*TInterfaceOrClass::StaticClass(), Function); + return ForEachAddOnForClass(*TInterfaceOrClass::StaticClass(), Function, TAddOnChildRule); } - EFlowForEachAddOnFunctionReturnValue ForEachAddOnForClass(const UClass& InterfaceOrClass, const FFlowNodeAddOnFunction& Function) const; + EFlowForEachAddOnFunctionReturnValue ForEachAddOnForClass(const UClass& InterfaceOrClass, const FFlowNodeAddOnFunction& Function, EFlowForEachAddOnChildRule AddOnChildRule = EFlowForEachAddOnChildRule::AllChildren) const; public: @@ -264,6 +285,10 @@ class FLOW_API UFlowNodeBase // Public only for TResolveDataPinWorkingData's use EFlowDataPinResolveResult TryResolveDataPinPrerequisites(const FName& PinName, const UFlowNode*& FlowNode, const FFlowPin*& FlowPin, EFlowPinType PinType) const; +protected: + + bool TryAddValueToFormatNamedArguments(const FFlowNamedDataPinProperty& NamedDataPinProperty, FFormatNamedArguments& InOutArguments) const; + public: ////////////////////////////////////////////////////////////////////////// @@ -288,6 +313,7 @@ class FLOW_API UFlowNodeBase TSubclassOf ReplacedBy; FFlowNodeEvent OnReconstructionRequested; + FFlowNodeEvent OnAddOnRequestedParentReconstruction; FFlowMessageLog ValidationLog; #endif // WITH_EDITORONLY_DATA @@ -314,7 +340,13 @@ class FLOW_API UFlowNodeBase // Called by owning FlowNode to add to its Status String. // (may be multi-line) virtual FString GetStatusString() const; -#endif // WITH_EDITOR + + EDataValidationResult ValidateNodeAndAddOns(FDataValidationContext& Context) const; + virtual EDataValidationResult ValidateNode(FDataValidationContext& Context) const { return EDataValidationResult::NotValidated; } + + void RequestReconstruction() const { (void) OnReconstructionRequested.ExecuteIfBound(); }; + +#endif protected: // Information displayed while node is working - displayed over node as NodeInfoPopup diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_CustomEventBase.h b/Source/Flow/Public/Nodes/Graph/FlowNode_CustomEventBase.h index 7e9944d25..62ca0dadf 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_CustomEventBase.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_CustomEventBase.h @@ -24,6 +24,6 @@ class FLOW_API UFlowNode_CustomEventBase : public UFlowNode #if WITH_EDITOR public: virtual FString GetNodeDescription() const override; - virtual EDataValidationResult ValidateNode() override; + virtual EDataValidationResult ValidateNode(FDataValidationContext& Context) const override; #endif }; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h b/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h index ea1e84515..0573362bc 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h @@ -20,12 +20,12 @@ class FLOW_API UFlowNode_DefineProperties : public UFlowNode, public IFlowDataPi // Instance-defined properties. // These will auto-generate a matching pin that is bound to its property as its data source. UPROPERTY(EditAnywhere, Category = "Configuration", DisplayName = Properties) - TArray OutputProperties; + TArray NamedProperties; public: #if WITH_EDITOR // IFlowContextPinSupplierInterface - virtual bool SupportsContextPins() const override { return Super::SupportsContextPins() || !OutputProperties.IsEmpty(); } + virtual bool SupportsContextPins() const override { return Super::SupportsContextPins() || !NamedProperties.IsEmpty(); } // -- // UObject @@ -37,6 +37,8 @@ class FLOW_API UFlowNode_DefineProperties : public UFlowNode, public IFlowDataPi // -- #endif + bool TryFormatTextWithNamedPropertiesAsParameters(const FText& FormatText, FText& OutFormattedText) const; + protected: virtual bool TryFindPropertyByRemappedPinName( const FName& RemappedPinName, diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_FormatText.h b/Source/Flow/Public/Nodes/Graph/FlowNode_FormatText.h new file mode 100644 index 000000000..02d5addea --- /dev/null +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_FormatText.h @@ -0,0 +1,42 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Nodes/Graph/FlowNode_DefineProperties.h" + +#include "FlowNode_FormatText.generated.h" + +/** + * Formats a text string using the standard UE FText formatting system + * using input pins as parameters and the output is delivered to OUTPIN_TextOutput + */ +UCLASS(NotBlueprintable, meta = (DisplayName = "Format Text", Keywords = "print")) +class FLOW_API UFlowNode_FormatText : public UFlowNode_DefineProperties +{ + GENERATED_UCLASS_BODY() + +private: + // Format text string + // (uses standard Unreal "FText" formatting: eg, {PinName} will refer to input called PinName) + // Note - complex types are exported "ToString" and InstancedStruct is not supported + UPROPERTY(EditAnywhere, Category = "Flow", meta = (DefaultForInputFlowPin, FlowPinType = Text)) + FText FormatText; + +protected: + +#if WITH_EDITOR +public: + virtual void UpdateNodeConfigText_Implementation() override; +#endif + + EFlowDataPinResolveResult TryResolveFormatText(const FName& PinName, FText& OutFormattedText) const; + +public: + // IFlowDataPinValueSupplierInterface + virtual FFlowDataPinResult_Name TrySupplyDataPinAsName_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_String TrySupplyDataPinAsString_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Text TrySupplyDataPinAsText_Implementation(const FName& PinName) const override; + // -- + + static const FName OUTPIN_TextOutput; +}; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h index b116d0332..85e39a9d4 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h @@ -75,7 +75,7 @@ class FLOW_API UFlowNode_SubGraph : public UFlowNode, public IFlowDataPinGenerat virtual FText GetNodeTitle() const override; virtual FString GetNodeDescription() const override; virtual UObject* GetAssetToEdit() override; - virtual EDataValidationResult ValidateNode() override; + virtual EDataValidationResult ValidateNode(FDataValidationContext& Context) const override; // UObject virtual void PostLoad() override; diff --git a/Source/Flow/Public/Types/FlowDataPinProperties.h b/Source/Flow/Public/Types/FlowDataPinProperties.h index d71277416..5c8b95f41 100644 --- a/Source/Flow/Public/Types/FlowDataPinProperties.h +++ b/Source/Flow/Public/Types/FlowDataPinProperties.h @@ -8,17 +8,18 @@ #include "Kismet/BlueprintFunctionLibrary.h" #include "Runtime/Launch/Resources/Version.h" #include "UObject/Class.h" +#include "Internationalization/Text.h" -#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 5 -#include "InstancedStruct.h" -#else +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 4 #include "StructUtils/InstancedStruct.h" +#else +#include "InstancedStruct.h" #endif - #include "FlowDataPinProperties.generated.h" class FStructProperty; class UScriptStruct; +class UFlowNodeBase; USTRUCT(BlueprintType, DisplayName = "Base - Flow DataPin Property") struct FFlowDataPinProperty @@ -29,7 +30,8 @@ struct FFlowDataPinProperty virtual ~FFlowDataPinProperty() { } - virtual EFlowPinType GetFlowPinType() const { return EFlowPinType::Invalid; } + FLOW_API virtual EFlowPinType GetFlowPinType() const { return EFlowPinType::Invalid; } + FLOW_API virtual bool IsInputProperty() const { return false; } #if WITH_EDITOR FLOW_API static FFlowPin CreateFlowPin(const FName& PinName, const TInstancedStruct& DataPinProperty); @@ -78,7 +80,7 @@ struct FFlowDataPinOutputProperty_Bool : public FFlowDataPinProperty FFlowDataPinOutputProperty_Bool() { } FFlowDataPinOutputProperty_Bool(bool InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Bool; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Bool; } }; // Wrapper struct for a int64 that will generate and link to a Data Pin with its same name @@ -97,7 +99,7 @@ struct FFlowDataPinOutputProperty_Int64 : public FFlowDataPinProperty FFlowDataPinOutputProperty_Int64() { } FFlowDataPinOutputProperty_Int64(int64 InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Int; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Int; } }; // Wrapper struct for a int32 that will generate and link to a Data Pin with its same name @@ -116,7 +118,7 @@ struct FFlowDataPinOutputProperty_Int32 : public FFlowDataPinProperty FFlowDataPinOutputProperty_Int32() { } FFlowDataPinOutputProperty_Int32(int32 InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Int; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Int; } }; // Wrapper struct for a Double (64bit float) that will generate and link to a Data Pin with its same name @@ -135,7 +137,7 @@ struct FFlowDataPinOutputProperty_Double : public FFlowDataPinProperty FFlowDataPinOutputProperty_Double() { } FFlowDataPinOutputProperty_Double(double InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Float; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Float; } }; // Wrapper struct for a Float (32bit) that will generate and link to a Data Pin with its same name @@ -154,7 +156,7 @@ struct FFlowDataPinOutputProperty_Float : public FFlowDataPinProperty FFlowDataPinOutputProperty_Float() { } FFlowDataPinOutputProperty_Float(float InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Float; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Float; } }; // Wrapper struct for a FName that will generate and link to a Data Pin with its same name @@ -173,7 +175,7 @@ struct FFlowDataPinOutputProperty_Name : public FFlowDataPinProperty FFlowDataPinOutputProperty_Name() { } FFlowDataPinOutputProperty_Name(const FName& InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Name; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Name; } }; // Wrapper struct for a FString that will generate and link to a Data Pin with its same name @@ -192,7 +194,7 @@ struct FFlowDataPinOutputProperty_String : public FFlowDataPinProperty FFlowDataPinOutputProperty_String() { } FFlowDataPinOutputProperty_String(const FString& InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::String; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::String; } }; // Wrapper struct for a FText that will generate and link to a Data Pin with its same name @@ -211,7 +213,7 @@ struct FFlowDataPinOutputProperty_Text : public FFlowDataPinProperty FFlowDataPinOutputProperty_Text() { } FFlowDataPinOutputProperty_Text(const FText& InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Text; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Text; } }; // Wrapper struct for an enum that will generate and link to a Data Pin with its same name @@ -247,7 +249,7 @@ struct FFlowDataPinOutputProperty_Enum : public FFlowDataPinProperty , EnumClass(InEnumClass) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Enum; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Enum; } #if WITH_EDITOR FLOW_API void OnEnumNameChanged(); @@ -270,7 +272,7 @@ struct FFlowDataPinOutputProperty_Vector : public FFlowDataPinProperty FFlowDataPinOutputProperty_Vector() {} FFlowDataPinOutputProperty_Vector(const FVector& InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Vector; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Vector; } }; // Wrapper struct for a FRotator that will generate and link to a Data Pin with its same name @@ -289,7 +291,7 @@ struct FFlowDataPinOutputProperty_Rotator : public FFlowDataPinProperty FFlowDataPinOutputProperty_Rotator() {} FFlowDataPinOutputProperty_Rotator(const FRotator& InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Rotator; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Rotator; } }; // Wrapper struct for a FTransform that will generate and link to a Data Pin with its same name @@ -308,7 +310,7 @@ struct FFlowDataPinOutputProperty_Transform : public FFlowDataPinProperty FFlowDataPinOutputProperty_Transform() {} FFlowDataPinOutputProperty_Transform(const FTransform& InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Transform; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Transform; } }; // Wrapper struct for a FGameplayTag that will generate and link to a Data Pin with its same name @@ -327,7 +329,7 @@ struct FFlowDataPinOutputProperty_GameplayTag : public FFlowDataPinProperty FFlowDataPinOutputProperty_GameplayTag() {} FFlowDataPinOutputProperty_GameplayTag(const FGameplayTag& InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::GameplayTag; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::GameplayTag; } }; // Wrapper struct for a FGameplayTagContainer that will generate and link to a Data Pin with its same name @@ -346,7 +348,7 @@ struct FFlowDataPinOutputProperty_GameplayTagContainer : public FFlowDataPinProp FFlowDataPinOutputProperty_GameplayTagContainer() {} FFlowDataPinOutputProperty_GameplayTagContainer(const FGameplayTagContainer& InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::GameplayTagContainer; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::GameplayTagContainer; } }; // Wrapper struct for a FInstancedStruct that will generate and link to a Data Pin with its same name @@ -365,7 +367,7 @@ struct FFlowDataPinOutputProperty_InstancedStruct : public FFlowDataPinProperty FFlowDataPinOutputProperty_InstancedStruct() {} FFlowDataPinOutputProperty_InstancedStruct(const FInstancedStruct& InValue) : Value(InValue) { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::InstancedStruct; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::InstancedStruct; } }; // Wrapper struct for a UObject that will generate and link to a Data Pin with its same name @@ -393,7 +395,7 @@ struct FFlowDataPinOutputProperty_Object : public FFlowDataPinProperty #if WITH_EDITORONLY_DATA UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract)) - TObjectPtr ClassFilter = nullptr; + TObjectPtr ClassFilter = UObject::StaticClass(); #endif // WITH_EDITORONLY_DATA public: @@ -401,7 +403,7 @@ struct FFlowDataPinOutputProperty_Object : public FFlowDataPinProperty FFlowDataPinOutputProperty_Object() {} FLOW_API FFlowDataPinOutputProperty_Object(UObject* InValue, UClass* InClassFilter = nullptr); - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Object; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Object; } UObject* GetObjectValue() const { return ReferenceValue ? ReferenceValue : InlineValue; } void SetObjectValue(UObject* InValue); @@ -429,7 +431,7 @@ struct FFlowDataPinOutputProperty_Class : public FFlowDataPinProperty #if WITH_EDITORONLY_DATA UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract)) - TObjectPtr ClassFilter = nullptr; + TObjectPtr ClassFilter = UObject::StaticClass(); #endif // WITH_EDITORONLY_DATA public: @@ -442,7 +444,7 @@ struct FFlowDataPinOutputProperty_Class : public FFlowDataPinProperty #endif { } - virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Class; } + FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Class; } #if WITH_EDITOR UClass* DeriveMetaClass(const FProperty& MetaDataProperty) const; @@ -456,8 +458,8 @@ struct FFlowDataPinOutputProperty_Class : public FFlowDataPinProperty // Wrapper for FFlowDataPinProperty that is used for flow nodes that add // dynamic properties, with associated data pins, on the flow node instance // (as opposed to C++ or blueprint compile-time). -USTRUCT(BlueprintType, DisplayName = "Flow Named Output DataPin Property") -struct FFlowNamedDataPinOutputProperty +USTRUCT(BlueprintType, DisplayName = "Flow Named DataPin Property") +struct FFlowNamedDataPinProperty { GENERATED_BODY() @@ -473,10 +475,13 @@ struct FFlowNamedDataPinOutputProperty public: - FFlowNamedDataPinOutputProperty() { } + FFlowNamedDataPinProperty() { } bool IsValid() const { return Name != NAME_None && DataPinProperty.GetPtr() != nullptr; } + bool IsInputProperty() const; + bool IsOutputProperty() const; + #if WITH_EDITOR FFlowPin CreateFlowPin() const { return FFlowDataPinProperty::CreateFlowPin(Name, DataPinProperty); } @@ -488,151 +493,185 @@ struct FFlowNamedDataPinOutputProperty // "Hidden" to keep them out of the TInstancedStruct selection list (but they can still be authored as properties in blueprint) // "DefaultForInputFlowPin" to change them to an Defaulted-Input property (rather than an output property) -USTRUCT(BlueprintType, DisplayName = "Bool - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Bool")) +USTRUCT(BlueprintType, DisplayName = "Bool - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Bool")) struct FFlowDataPinInputProperty_Bool : public FFlowDataPinOutputProperty_Bool { GENERATED_BODY() FFlowDataPinInputProperty_Bool(bool InValue = false) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Int64 - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Int")) +USTRUCT(BlueprintType, DisplayName = "Int64 - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Int")) struct FFlowDataPinInputProperty_Int64 : public FFlowDataPinOutputProperty_Int64 { GENERATED_BODY() FFlowDataPinInputProperty_Int64(int64 InValue = 0) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Int - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Int")) +USTRUCT(BlueprintType, DisplayName = "Int - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Int")) struct FFlowDataPinInputProperty_Int32 : public FFlowDataPinOutputProperty_Int32 { GENERATED_BODY() FFlowDataPinInputProperty_Int32(int32 InValue = 0) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Double (float64) - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Float")) +USTRUCT(BlueprintType, DisplayName = "Double (float64) - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Float")) struct FFlowDataPinInputProperty_Double : public FFlowDataPinOutputProperty_Double { GENERATED_BODY() FFlowDataPinInputProperty_Double(double InValue = 0.0) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Float - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Float")) +USTRUCT(BlueprintType, DisplayName = "Float - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Float")) struct FFlowDataPinInputProperty_Float : public FFlowDataPinOutputProperty_Float { GENERATED_BODY() FFlowDataPinInputProperty_Float(float InValue = 0.0f) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Name - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Name")) +USTRUCT(BlueprintType, DisplayName = "Name - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Name")) struct FFlowDataPinInputProperty_Name : public FFlowDataPinOutputProperty_Name { GENERATED_BODY() FFlowDataPinInputProperty_Name() : Super() { } FFlowDataPinInputProperty_Name(const FName& InValue) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "String - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "String")) +USTRUCT(BlueprintType, DisplayName = "String - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "String")) struct FFlowDataPinInputProperty_String : public FFlowDataPinOutputProperty_String { GENERATED_BODY() FFlowDataPinInputProperty_String() : Super() { } FFlowDataPinInputProperty_String(const FString& InValue) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Text - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Text")) +USTRUCT(BlueprintType, DisplayName = "Text - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Text")) struct FFlowDataPinInputProperty_Text : public FFlowDataPinOutputProperty_Text { GENERATED_BODY() FFlowDataPinInputProperty_Text() : Super() { } FFlowDataPinInputProperty_Text(const FText& InValue) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Enum - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Enum")) +USTRUCT(BlueprintType, DisplayName = "Enum - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Enum")) struct FFlowDataPinInputProperty_Enum : public FFlowDataPinOutputProperty_Enum { GENERATED_BODY() FFlowDataPinInputProperty_Enum() : Super() { } FFlowDataPinInputProperty_Enum(const FName& InValue, UEnum* InEnumClass) : Super(InValue, InEnumClass) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Vector - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Vector")) +USTRUCT(BlueprintType, DisplayName = "Vector - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Vector")) struct FFlowDataPinInputProperty_Vector : public FFlowDataPinOutputProperty_Vector { GENERATED_BODY() FFlowDataPinInputProperty_Vector() : Super() { } FFlowDataPinInputProperty_Vector(const FVector& InValue) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Rotator - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Rotator")) +USTRUCT(BlueprintType, DisplayName = "Rotator - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Rotator")) struct FFlowDataPinInputProperty_Rotator : public FFlowDataPinOutputProperty_Rotator { GENERATED_BODY() FFlowDataPinInputProperty_Rotator() : Super() { } FFlowDataPinInputProperty_Rotator(const FRotator& InValue) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Transform - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Transform")) +USTRUCT(BlueprintType, DisplayName = "Transform - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Transform")) struct FFlowDataPinInputProperty_Transform : public FFlowDataPinOutputProperty_Transform { GENERATED_BODY() FFlowDataPinInputProperty_Transform() : Super() { } FFlowDataPinInputProperty_Transform(const FTransform& InValue) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "GameplayTag - Input Flow Data Pin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "GameplayTag")) +USTRUCT(BlueprintType, DisplayName = "GameplayTag - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "GameplayTag")) struct FFlowDataPinInputProperty_GameplayTag : public FFlowDataPinOutputProperty_GameplayTag { GENERATED_BODY() FFlowDataPinInputProperty_GameplayTag() : Super() { } FFlowDataPinInputProperty_GameplayTag(const FGameplayTag& InValue) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "GameplayTagContainer - Input Flow DataPin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "GameplayTagContainer")) +USTRUCT(BlueprintType, DisplayName = "GameplayTagContainer - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "GameplayTagContainer")) struct FFlowDataPinInputProperty_GameplayTagContainer : public FFlowDataPinOutputProperty_GameplayTagContainer { GENERATED_BODY() FFlowDataPinInputProperty_GameplayTagContainer() : Super() { } FFlowDataPinInputProperty_GameplayTagContainer(const FGameplayTagContainer& InValue) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "InstancedStruct - Input Flow DataPin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "InstancedStruct")) +USTRUCT(BlueprintType, DisplayName = "InstancedStruct - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "InstancedStruct")) struct FFlowDataPinInputProperty_InstancedStruct : public FFlowDataPinOutputProperty_InstancedStruct { GENERATED_BODY() FFlowDataPinInputProperty_InstancedStruct() : Super() { } FFlowDataPinInputProperty_InstancedStruct(const FInstancedStruct& InValue) : Super(InValue) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Object - Input Flow DataPin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Object")) +USTRUCT(BlueprintType, DisplayName = "Object - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Object")) struct FFlowDataPinInputProperty_Object : public FFlowDataPinOutputProperty_Object { GENERATED_BODY() FFlowDataPinInputProperty_Object() : Super() { } FFlowDataPinInputProperty_Object(UObject* InValue, UClass* InClassFilter) : Super(InValue, InClassFilter) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Class - Input Flow DataPin Property", meta = (Hidden, DefaultForInputFlowPin, FlowPinType = "Class")) +USTRUCT(BlueprintType, DisplayName = "Class - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Class")) struct FFlowDataPinInputProperty_Class : public FFlowDataPinOutputProperty_Class { GENERATED_BODY() FFlowDataPinInputProperty_Class() : Super() { } FFlowDataPinInputProperty_Class(const FSoftClassPath& InValue, UClass* InClassFilter) : Super(InValue, InClassFilter) { } + + FLOW_API virtual bool IsInputProperty() const override { return true; } }; diff --git a/Source/Flow/Public/Types/FlowDataPinResults.h b/Source/Flow/Public/Types/FlowDataPinResults.h index 0d2e7f61e..838a57ef9 100644 --- a/Source/Flow/Public/Types/FlowDataPinResults.h +++ b/Source/Flow/Public/Types/FlowDataPinResults.h @@ -51,6 +51,7 @@ struct FFlowDataPinResult_Bool : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Bool() { } + FLOW_API FFlowDataPinResult_Bool(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Bool(bool InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -70,6 +71,7 @@ struct FFlowDataPinResult_Int : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Int() { } + FLOW_API FFlowDataPinResult_Int(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Int(int64 InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -89,6 +91,7 @@ struct FFlowDataPinResult_Float : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Float() { } + FLOW_API FFlowDataPinResult_Float(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Float(double InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -108,6 +111,7 @@ struct FFlowDataPinResult_Name : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Name() { } + FLOW_API FFlowDataPinResult_Name(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Name(const FName& InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -131,6 +135,7 @@ struct FFlowDataPinResult_String : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_String() { } + FLOW_API FFlowDataPinResult_String(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_String(const FString& InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -154,6 +159,7 @@ struct FFlowDataPinResult_Text : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Text() { } + FLOW_API FFlowDataPinResult_Text(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Text(const FText& InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -182,12 +188,12 @@ struct FFlowDataPinResult_Enum : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Enum() { } + FLOW_API FFlowDataPinResult_Enum(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Enum(const FName& InValue, UEnum* InEnumClass) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) , EnumClass(InEnumClass) { } - FLOW_API explicit FFlowDataPinResult_Enum(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API explicit FFlowDataPinResult_Enum(uint8 InEnumAsIntValue, UEnum& InEnumClass) : Super(EFlowDataPinResolveResult::Success) , Value() @@ -252,6 +258,7 @@ struct FFlowDataPinResult_Vector : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Vector() { } + FLOW_API FFlowDataPinResult_Vector(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Vector(const FVector& InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -271,6 +278,7 @@ struct FFlowDataPinResult_Rotator : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Rotator() { } + FLOW_API FFlowDataPinResult_Rotator(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Rotator(const FRotator& InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -290,6 +298,7 @@ struct FFlowDataPinResult_Transform : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Transform() { } + FLOW_API FFlowDataPinResult_Transform(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Transform(const FTransform& InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -309,6 +318,7 @@ struct FFlowDataPinResult_GameplayTag : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_GameplayTag() { } + FLOW_API FFlowDataPinResult_GameplayTag(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_GameplayTag(const FGameplayTag& InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -328,6 +338,7 @@ struct FFlowDataPinResult_GameplayTagContainer : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_GameplayTagContainer() { } + FLOW_API FFlowDataPinResult_GameplayTagContainer(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_GameplayTagContainer(const FGameplayTagContainer& InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -347,6 +358,7 @@ struct FFlowDataPinResult_InstancedStruct : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_InstancedStruct() { } + FLOW_API FFlowDataPinResult_InstancedStruct(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_InstancedStruct(const FInstancedStruct& InValue) : Super(EFlowDataPinResolveResult::Success) , Value(InValue) @@ -366,6 +378,7 @@ struct FFlowDataPinResult_Object : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Object() { } + FLOW_API FFlowDataPinResult_Object(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Object(UObject* InValue); FLOW_API void SetValueFromPropertyWrapper(const FFlowDataPinOutputProperty_Object& InPropertyWrapper); @@ -393,6 +406,7 @@ struct FFlowDataPinResult_Class : public FFlowDataPinResult public: FLOW_API FFlowDataPinResult_Class() { } + FLOW_API FFlowDataPinResult_Class(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Class(const FSoftClassPath& InValuePath); FLOW_API FFlowDataPinResult_Class(UClass* InValueClass); diff --git a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp index 32c270716..3c268313e 100644 --- a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp +++ b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp @@ -1,10 +1,11 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Asset/FlowObjectDiff.h" #include "Asset/FlowDiffControl.h" #include "Nodes/FlowNodeBase.h" #include "EdGraph/EdGraph.h" +#include "Runtime/Launch/Resources/Version.h" #include "Graph/Nodes/FlowGraphNode.h" #include "SBlueprintDiff.h" diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.cpp index c9ed8c249..194def98c 100644 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.cpp +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.cpp @@ -2,11 +2,11 @@ #include "DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h" -FText FFlowNamedDataPinOutputPropertyCustomization::BuildHeaderText() const +FText FFlowNamedDataPinPropertyCustomization::BuildHeaderText() const { - if (const FFlowNamedDataPinOutputProperty* FlowNamedDataPinOutputProperty = IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) + if (const FFlowNamedDataPinProperty* FlowNamedDataPinProperty = IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) { - return FlowNamedDataPinOutputProperty->BuildHeaderText(); + return FlowNamedDataPinProperty->BuildHeaderText(); } return Super::BuildHeaderText(); diff --git a/Source/FlowEditor/Private/FlowEditorModule.cpp b/Source/FlowEditor/Private/FlowEditorModule.cpp index 5f0893b1c..94582f2f7 100644 --- a/Source/FlowEditor/Private/FlowEditorModule.cpp +++ b/Source/FlowEditor/Private/FlowEditorModule.cpp @@ -233,7 +233,7 @@ void FFlowEditorModule::RegisterDetailCustomizations() RegisterCustomClassLayout(UFlowNode_SubGraph::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FFlowNode_SubGraphDetails::MakeInstance)); RegisterCustomStructLayout(*FFlowActorOwnerComponentRef::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowActorOwnerComponentRefCustomization::MakeInstance)); RegisterCustomStructLayout(*FFlowPin::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowPinCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowNamedDataPinOutputProperty::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowNamedDataPinOutputPropertyCustomization::MakeInstance)); + RegisterCustomStructLayout(*FFlowNamedDataPinProperty::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowNamedDataPinPropertyCustomization::MakeInstance)); RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Bool::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_BoolCustomization::MakeInstance)); RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Int64::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_Int64Customization::MakeInstance)); diff --git a/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp b/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp index 21004681a..c289e0d15 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp @@ -28,6 +28,7 @@ #include "Engine/MemberReference.h" #include "Kismet2/KismetEditorUtilities.h" #include "ScopedTransaction.h" +#include "Runtime/Launch/Resources/Version.h" #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6 #include "Kismet/BlueprintTypeConversions.h" diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp index 8edd89c8d..5da29c4ef 100644 --- a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp @@ -129,9 +129,10 @@ void UFlowGraphNode::PostPlacedNewNode() SubscribeToExternalChanges(); // note: NodeInstance can be already spawned by paste operation, don't override it + if (NodeInstanceClass.IsPending()) { - NodeInstanceClass.LoadSynchronous(); + (void) NodeInstanceClass.LoadSynchronous(); } if (NodeInstance == nullptr) @@ -148,6 +149,9 @@ void UFlowGraphNode::PostPlacedNewNode() } } } + + // We subscribe to external changes to the Node Instance after we have tried to ensure that the node instance exists. + SubscribeToExternalChanges(); } void UFlowGraphNode::PrepareForCopying() @@ -193,6 +197,19 @@ void UFlowGraphNode::SubscribeToExternalChanges() if (NodeInstance) { NodeInstance->OnReconstructionRequested.BindUObject(this, &UFlowGraphNode::OnExternalChange); + NodeInstance->OnAddOnRequestedParentReconstruction.BindUObject(this, &UFlowGraphNode::ReportExternalChangeToRootFlowGraphNode); + } +} + +void UFlowGraphNode::ReportExternalChangeToRootFlowGraphNode() +{ + if (bIsSubNode) + { + GetParentNode()->ReportExternalChangeToRootFlowGraphNode(); + } + else + { + OnExternalChange(); } } @@ -443,7 +460,8 @@ void UFlowGraphNode::RewireOldPinsToNewPins(TArray& InOldPins) case EGPD_Output: OutputPins.Add(OrphanedPin); break; - default: ; + default: + break; } } } @@ -1104,7 +1122,7 @@ void UFlowGraphNode::SetSignalMode(const EFlowSignalMode Mode) if (UFlowNode* FlowNode = Cast(NodeInstance)) { FlowNode->SignalMode = Mode; - OnSignalModeChanged.ExecuteIfBound(); + (void) OnSignalModeChanged.ExecuteIfBound(); } } @@ -1537,7 +1555,7 @@ bool UFlowGraphNode::RefreshNodeClass() { if (NodeInstanceClass.IsPending()) { - NodeInstanceClass.LoadSynchronous(); + (void) NodeInstanceClass.LoadSynchronous(); } if (NodeInstanceClass.IsValid()) @@ -1671,9 +1689,8 @@ bool CheckPinsMatch(const TArray& LeftPins, const TArray& Ri auto PinsAreEqualPredicate = [&Left](const FFlowPin& Right) { const bool bNameMatch = Left.PinName == Right.PinName; - const bool bFriendlyNameMatch = Left.PinFriendlyName.EqualTo(Right.PinFriendlyName); const bool bTypeMatch = Left.GetPinType() == Right.GetPinType(); - return bNameMatch && bFriendlyNameMatch && bTypeMatch; + return bNameMatch && bTypeMatch; }; // For each required pin, make sure the existing pins array contains a pin that matches by name and type @@ -1700,7 +1717,7 @@ bool CheckPinsMatch(const TArray& GraphPins, const TArrayPinName == FlowNodePin.PinName && GraphNodePin->PinFriendlyName.EqualTo(FlowNodePin.PinFriendlyName); + return GraphNodePin->PinName == FlowNodePin.PinName; })) { // Could not match the pin from the flow node with any of the EdPins array. diff --git a/Source/FlowEditor/Private/Graph/Widgets/SGraphEditorActionMenuFlow.cpp b/Source/FlowEditor/Private/Graph/Widgets/SGraphEditorActionMenuFlow.cpp index d429228c6..1cf4598f7 100644 --- a/Source/FlowEditor/Private/Graph/Widgets/SGraphEditorActionMenuFlow.cpp +++ b/Source/FlowEditor/Private/Graph/Widgets/SGraphEditorActionMenuFlow.cpp @@ -1,6 +1,8 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Graph/Widgets/SGraphEditorActionMenuFlow.h" + +#include "Graph/Nodes/FlowGraphNode.h" #include "Graph/FlowGraphSchema.h" #include "EdGraph/EdGraph.h" @@ -14,6 +16,7 @@ #include "Templates/Casts.h" #include "Types/SlateStructs.h" #include "Widgets/Layout/SBox.h" +#include "Runtime/Launch/Resources/Version.h" SGraphEditorActionMenuFlow::~SGraphEditorActionMenuFlow() { diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h index f24e3192e..f63e1e18b 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h @@ -7,12 +7,12 @@ #include "Types/FlowDataPinProperties.h" // Details customization for FFlowPin -class FFlowNamedDataPinOutputPropertyCustomization : public IFlowExtendedPropertyTypeCustomization +class FFlowNamedDataPinPropertyCustomization : public IFlowExtendedPropertyTypeCustomization { typedef IFlowExtendedPropertyTypeCustomization Super; public: - static TSharedRef MakeInstance() { return MakeShareable(new FFlowNamedDataPinOutputPropertyCustomization()); } + static TSharedRef MakeInstance() { return MakeShareable(new FFlowNamedDataPinPropertyCustomization()); } protected: diff --git a/Source/FlowEditor/Public/FlowEditorModule.h b/Source/FlowEditor/Public/FlowEditorModule.h index 71bc523bc..018bfd314 100644 --- a/Source/FlowEditor/Public/FlowEditorModule.h +++ b/Source/FlowEditor/Public/FlowEditorModule.h @@ -6,6 +6,7 @@ #include "IAssetTypeActions.h" #include "Modules/ModuleInterface.h" #include "PropertyEditorDelegates.h" +#include "Toolkits/AssetEditorToolkit.h" #include "Toolkits/IToolkit.h" class FSlateStyleSet; diff --git a/Source/FlowEditor/Public/Graph/FlowGraphConnectionDrawingPolicy.h b/Source/FlowEditor/Public/Graph/FlowGraphConnectionDrawingPolicy.h index d38f55d85..93dd074bd 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphConnectionDrawingPolicy.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphConnectionDrawingPolicy.h @@ -4,6 +4,7 @@ #include "ConnectionDrawingPolicy.h" #include "EdGraphUtilities.h" +#include "Runtime/Launch/Resources/Version.h" UENUM() enum class EFlowConnectionDrawType : uint8 diff --git a/Source/FlowEditor/Public/Graph/FlowGraphEditor.h b/Source/FlowEditor/Public/Graph/FlowGraphEditor.h index c910d60d4..085b1fe47 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphEditor.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphEditor.h @@ -4,6 +4,7 @@ #include "GraphEditor.h" #include "Widgets/DeclarativeSyntaxSupport.h" +#include "Runtime/Launch/Resources/Version.h" #include "FlowGraph.h" diff --git a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h index 9aa8d2db4..5341e877f 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h @@ -4,6 +4,8 @@ #include "EdGraph/EdGraphSchema.h" #include "Templates/SubclassOf.h" +#include "Runtime/Launch/Resources/Version.h" + #include "FlowGraphSchema.generated.h" class UFlowAsset; diff --git a/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h index cabe0854d..39fdf6058 100644 --- a/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h +++ b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h @@ -69,6 +69,7 @@ class FLOWEDITOR_API UFlowGraphNode : public UEdGraphNode private: void SubscribeToExternalChanges(); + void ReportExternalChangeToRootFlowGraphNode(); void OnExternalChange(); public: