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: