diff --git a/Config/DefaultFlow.ini b/Config/DefaultFlow.ini index fe550471..703e8278 100644 --- a/Config/DefaultFlow.ini +++ b/Config/DefaultFlow.ini @@ -4,3 +4,5 @@ +PropertyRedirects=(OldName="FlowGraphNode.FlowNode",NewName="FlowGraphNode.NodeInstance") +StructRedirects=(OldName="/Script/Flow.FlowNamedDataPinOutputProperty",NewName="/Script/Flow.FlowNamedDataPinProperty") +PropertyRedirects=(OldName="FlowNode_DefineProperties.OutputProperties",NewName="NamedProperties") ++FunctionRedirects=(OldName="/Script/Flow.FlowSubsystem.FinishRootFlow",NewName="/Script/Flow.FlowSubsystem.FinishAndDeinitializeRootFlow") ++FunctionRedirects=(OldName="/Script/Flow.FlowSubsystem.FinishAllRootFlows",NewName="/Script/Flow.FlowSubsystem.FinishAndDeinitializeAllRootFlows") \ No newline at end of file diff --git a/LICENSE b/LICENSE index 2ab42e29..7670746d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -MIT License +MIT License Copyright (c) https://github.com/MothCocoon/FlowGraph/graphs/contributors diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index aba46958..4258682f 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -9,6 +9,8 @@ #include "Asset/FlowAssetParams.h" #include "Asset/FlowAssetParamsUtils.h" #include "Interfaces/FlowExecutionGate.h" +#include "Interfaces/FlowGraphOutputDataReceiverInterface.h" +#include "Types/FlowNamedDataPinProperty.h" #include "Nodes/FlowNodeBase.h" #include "Nodes/Graph/FlowNode_CustomInput.h" #include "Nodes/Graph/FlowNode_CustomOutput.h" @@ -16,6 +18,7 @@ #include "Nodes/Graph/FlowNode_SubGraph.h" #include "Policies/FlowPinConnectionPolicy.h" #include "Policies/FlowPreloadPolicy.h" +#include "Types/FlowAutoDataPinsWorkingData.h" #include "Types/FlowDataPinValue.h" #include "Types/FlowStructUtils.h" @@ -25,6 +28,7 @@ #include "Algo/AnyOf.h" #if WITH_EDITOR +#include "Nodes/Graph/FlowNode_SetGraphOutput.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetToolsModule.h" #include "ContentBrowserModule.h" @@ -77,11 +81,26 @@ void UFlowAsset::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEv { Super::PostEditChangeProperty(PropertyChangedEvent); - if (PropertyChangedEvent.Property && (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UFlowAsset, CustomInputs) - || PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UFlowAsset, CustomOutputs))) + const FName ChangedPropertyName = PropertyChangedEvent.GetPropertyName(); + const FName ChangedMemberPropertyName = PropertyChangedEvent.GetMemberPropertyName(); + if (PropertyChangedEvent.Property && (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(UFlowAsset, CustomInputs) + || ChangedPropertyName == GET_MEMBER_NAME_CHECKED(UFlowAsset, CustomOutputs) + || ChangedMemberPropertyName == GET_MEMBER_NAME_CHECKED(UFlowAsset, OutputDataPinDeclarations))) { OnSubGraphReconstructionRequested.ExecuteIfBound(); } + + if (PropertyChangedEvent.Property && ChangedMemberPropertyName == GET_MEMBER_NAME_CHECKED(UFlowAsset, OutputDataPinDeclarations)) + { + for (const TPair& NodePair : GetNodes()) + { + UFlowNode_SetGraphOutput* SetOutputNode = Cast(NodePair.Value); + if (IsValid(SetOutputNode) && SetOutputNode->TryUpdateAutoDataPins()) + { + SetOutputNode->OnReconstructionRequested.ExecuteIfBound(); + } + } + } } void UFlowAsset::PostDuplicate(bool bDuplicateForPIE) @@ -896,7 +915,7 @@ void UFlowAsset::ClearInstances() { if (ActiveInstances.IsValidIndex(i) && ActiveInstances[i]) { - ActiveInstances[i]->FinishFlow(EFlowFinishPolicy::Keep); + ActiveInstances[i]->FinishFlowAndDeinitializeInstance(EFlowFinishPolicy::Keep); } } @@ -1023,6 +1042,12 @@ AActor* UFlowAsset::TryFindActorOwner() const return nullptr; } +void UFlowAsset::FinishFlowAndDeinitializeInstance(const EFlowFinishPolicy InFinishPolicy) +{ + FinishFlow(InFinishPolicy); + DeinitializeInstance(); +} + void UFlowAsset::PreStartFlow() { ResetNodes(); @@ -1043,8 +1068,10 @@ void UFlowAsset::PreStartFlow() #endif } -void UFlowAsset::StartFlow(IFlowDataPinValueSupplierInterface* DataPinValueSupplier) +void UFlowAsset::StartFlow(IFlowDataPinValueSupplierInterface* DataPinValueSupplier, IFlowGraphOutputDataReceiverInterface* InOutputDataReceiver) { + InitializeOutputDataReceiverAndValues(InOutputDataReceiver); + PreStartFlow(); if (UFlowNode* ConnectedEntryNode = GetDefaultEntryNode()) @@ -1074,6 +1101,11 @@ void UFlowAsset::FinishNode(UFlowNode* Node) // if graph reached Finish and this asset instance was created by SubGraph node if (Node->CanFinishGraph()) { + if (IFlowGraphOutputDataReceiverInterface* Receiver = Cast(OutputDataReceiver.Get())) + { + Receiver->ReceiveOutputDataSnapshot(OutputDataPinValues); + } + if (NodeOwningThisAssetInstance.IsValid()) { NodeOwningThisAssetInstance.Get()->TriggerFirstOutput(true); @@ -1081,13 +1113,14 @@ void UFlowAsset::FinishNode(UFlowNode* Node) return; } - // if this instance is a Root Flow, we need to deregister it from the subsystem first + // if this instance is a Root Flow, we need to deregister it from the subsystem first. This will + // finalize and deinitialize the root flow. if (Owner.IsValid()) { const TSet& RootFlowInstances = GetFlowSubsystem()->GetRootInstancesByOwner(Owner.Get()); if (RootFlowInstances.Contains(this)) { - GetFlowSubsystem()->FinishRootFlow(Owner.Get(), TemplateAsset, EFlowFinishPolicy::Keep); + GetFlowSubsystem()->FinishAndDeinitializeRootFlow(Owner.Get(), TemplateAsset, EFlowFinishPolicy::Keep); return; } @@ -1108,7 +1141,7 @@ void UFlowAsset::ResetNodes() RecordedNodes.Empty(); } -void UFlowAsset::FinishFlow(const EFlowFinishPolicy InFinishPolicy, const bool bRemoveInstance /*= true*/) +void UFlowAsset::FinishFlow(const EFlowFinishPolicy InFinishPolicy) { FinishPolicy = InFinishPolicy; @@ -1119,13 +1152,8 @@ void UFlowAsset::FinishFlow(const EFlowFinishPolicy InFinishPolicy, const bool b { Node->Deactivate(); } - ActiveNodes.Empty(); - // provides option to finish game-specific logic prior to removing asset instance - if (bRemoveInstance) - { - DeinitializeInstance(); - } + ActiveNodes.Empty(); } UFlowSubsystem* UFlowAsset::GetFlowSubsystem() const @@ -1148,6 +1176,11 @@ TWeakObjectPtr UFlowAsset::GetFlowInstance(UFlowNode_SubGraph* SubGr return ActiveSubGraphs.FindRef(SubGraphNode); } +FName UFlowAsset::GetDisplayName() const +{ + return GetFName(); +} + void UFlowAsset::InitializePreloadPolicy() { if (PreloadPolicy.IsValid()) @@ -1174,6 +1207,50 @@ const FFlowPreloadPolicy& UFlowAsset::GetPreloadPolicy() const return PreloadPolicy.Get(); } +void UFlowAsset::InitializeOutputDataReceiverAndValues(IFlowGraphOutputDataReceiverInterface* InOutputDataReceiver) +{ + OutputDataReceiver = Cast(InOutputDataReceiver); + + // Initialize the live output store from the template asset's declarations + OutputDataPinValues.Values.Reset(); + + if (const UFlowAsset* Template = TemplateAsset.Get()) + { + for (const FFlowNamedDataPinProperty& Declaration : Template->OutputDataPinDeclarations) + { + if (Declaration.IsValid()) + { + OutputDataPinValues.Values.Add(Declaration.Name, Declaration.DataPinValue); + } + else + { + UE_LOG(LogFlow, Warning, TEXT("Invalid OutputDataPin %s"), *Declaration.Name.ToString()); + } + } + } +} + +void UFlowAsset::WriteOutputDataPinValue(const FName& PinName, const TInstancedStruct& Value) +{ + if (OutputDataPinValues.Values.Contains(PinName)) + { + OutputDataPinValues.Values[PinName] = Value; + } + else + { + UE_LOG(LogFlow, Warning, TEXT("Could not find pin named %s in WriteOutputDataPinValue"), *PinName.ToString()); + } +} + +void UFlowAsset::FlushOutputDataPinValuesToReceiver() +{ + if (IFlowGraphOutputDataReceiverInterface* Receiver = Cast(OutputDataReceiver.Get())) + { + // Do an immediate push to the receiver + Receiver->ReceiveOutputDataSnapshot(OutputDataPinValues); + } +} + void UFlowAsset::TriggerCustomInput(const FName& EventName, IFlowDataPinValueSupplierInterface* DataPinValueSupplier) { for (UFlowNode_CustomInput* CustomInputNode : CustomInputNodes) diff --git a/Source/Flow/Private/FlowComponent.cpp b/Source/Flow/Private/FlowComponent.cpp index da1dbe1f..62ca1dbc 100644 --- a/Source/Flow/Private/FlowComponent.cpp +++ b/Source/Flow/Private/FlowComponent.cpp @@ -99,7 +99,7 @@ void UFlowComponent::UnregisterWithFlowSubsystem() { if (UFlowSubsystem* FlowSubsystem = GetFlowSubsystem()) { - FlowSubsystem->FinishAllRootFlows(this, EFlowFinishPolicy::Keep); + FlowSubsystem->FinishAndDeinitializeAllRootFlows(this, EFlowFinishPolicy::Keep); FlowSubsystem->UnregisterComponent(this); } } @@ -461,7 +461,7 @@ void UFlowComponent::FinishRootFlow(UFlowAsset* TemplateAsset, const EFlowFinish { if (UFlowSubsystem* FlowSubsystem = GetFlowSubsystem()) { - FlowSubsystem->FinishRootFlow(this, TemplateAsset, FinishPolicy); + FlowSubsystem->FinishAndDeinitializeRootFlow(this, TemplateAsset, FinishPolicy); } } diff --git a/Source/Flow/Private/FlowSubsystem.cpp b/Source/Flow/Private/FlowSubsystem.cpp index 65c49e9b..186aba16 100644 --- a/Source/Flow/Private/FlowSubsystem.cpp +++ b/Source/Flow/Private/FlowSubsystem.cpp @@ -85,7 +85,11 @@ void UFlowSubsystem::StartRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const { if (UFlowAsset* NewFlow = CreateRootFlow(Owner, FlowAsset, bAllowMultipleInstances)) { - NewFlow->StartFlow(DataPinValueSupplier.GetInterface()); + // TODO (gtaylor) Not implementing output parameters "yet", + // see Subgraph node for the pioneer implementation. + constexpr IFlowGraphOutputDataReceiverInterface* OutputDataReceiverInterface = nullptr; + + NewFlow->StartFlow(DataPinValueSupplier.GetInterface(), OutputDataReceiverInterface); } } #if WITH_EDITOR @@ -123,7 +127,7 @@ UFlowAsset* UFlowSubsystem::CreateRootFlow(UObject* Owner, UFlowAsset* FlowAsset return NewFlow; } -void UFlowSubsystem::FinishRootFlow(UObject* Owner, UFlowAsset* TemplateAsset, const EFlowFinishPolicy FinishPolicy) +void UFlowSubsystem::FinishAndDeinitializeRootFlow(UObject* Owner, UFlowAsset* TemplateAsset, const EFlowFinishPolicy FinishPolicy) { UFlowAsset* InstanceToFinish = nullptr; @@ -139,11 +143,11 @@ void UFlowSubsystem::FinishRootFlow(UObject* Owner, UFlowAsset* TemplateAsset, c if (InstanceToFinish) { RootInstances.Remove(InstanceToFinish); - InstanceToFinish->FinishFlow(FinishPolicy); + InstanceToFinish->FinishFlowAndDeinitializeInstance(FinishPolicy); } } -void UFlowSubsystem::FinishAllRootFlows(UObject* Owner, const EFlowFinishPolicy FinishPolicy) +void UFlowSubsystem::FinishAndDeinitializeAllRootFlows(UObject* Owner, const EFlowFinishPolicy FinishPolicy) { TArray InstancesToFinish; @@ -158,7 +162,7 @@ void UFlowSubsystem::FinishAllRootFlows(UObject* Owner, const EFlowFinishPolicy for (UFlowAsset* InstanceToFinish : InstancesToFinish) { RootInstances.Remove(InstanceToFinish); - InstanceToFinish->FinishFlow(FinishPolicy); + InstanceToFinish->FinishFlowAndDeinitializeInstance(FinishPolicy); } } @@ -182,19 +186,41 @@ UFlowAsset* UFlowSubsystem::CreateSubFlow(UFlowNode_SubGraph* SubGraphNode, cons // get instanced asset from map - in case it was already instanced by calling CreateSubFlow() with bPreloading == true UFlowAsset* AssetInstance = InstancedSubFlows[SubGraphNode]; - AssetInstance->NodeOwningThisAssetInstance = SubGraphNode; + if (!AssetInstance->NodeOwningThisAssetInstance.IsValid()) + { + AssetInstance->NodeOwningThisAssetInstance = SubGraphNode; + } + check(AssetInstance->NodeOwningThisAssetInstance == SubGraphNode); + SubGraphNode->GetFlowAsset()->ActiveSubGraphs.Add(SubGraphNode, AssetInstance); // don't activate Start Node if we're loading Sub Graph from SaveGame if (SavedInstanceName.IsEmpty()) { - AssetInstance->StartFlow(SubGraphNode); + AssetInstance->StartFlow(SubGraphNode, SubGraphNode); } } return NewInstance; } +void UFlowSubsystem::FinishSubFlow(UFlowNode_SubGraph* SubGraphNode, const EFlowFinishPolicy FinishPolicy) +{ + if (InstancedSubFlows.Contains(SubGraphNode)) + { + // The flow asset running on the subgraph node. + UFlowAsset* SubgraphFlowAsset = InstancedSubFlows[SubGraphNode]; + + // This is the flow asset that has the subgraph node. Do not confuse with the flow asset that the node is running. + // Remove the subgraph flow from the owning flow active subgraph list. + UFlowAsset* SubgraphNodeParentFlow = SubGraphNode->GetFlowAsset(); + SubgraphNodeParentFlow->ActiveSubGraphs.Remove(SubGraphNode); + + // Finish the flow but do not remove the instance. + SubgraphFlowAsset->FinishFlow(FinishPolicy); + } +} + void UFlowSubsystem::RemoveSubFlow(UFlowNode_SubGraph* SubGraphNode, const EFlowFinishPolicy FinishPolicy) { if (InstancedSubFlows.Contains(SubGraphNode)) @@ -204,7 +230,12 @@ void UFlowSubsystem::RemoveSubFlow(UFlowNode_SubGraph* SubGraphNode, const EFlow SubGraphNode->GetFlowAsset()->ActiveSubGraphs.Remove(SubGraphNode); InstancedSubFlows.Remove(SubGraphNode); - AssetInstance->FinishFlow(FinishPolicy); + if (AssetInstance->IsActive()) + { + AssetInstance->FinishFlow(FinishPolicy); + } + + AssetInstance->DeinitializeInstance(); // Make sure to set the NodeOwningThisAssetInstance after the FinishFlow call, as it may be needed in the FinishFlow method AssetInstance->NodeOwningThisAssetInstance = nullptr; diff --git a/Source/Flow/Private/LevelSequence/FlowLevelSequenceActor.cpp b/Source/Flow/Private/LevelSequence/FlowLevelSequenceActor.cpp index 88b0f35c..d99f05ce 100644 --- a/Source/Flow/Private/LevelSequence/FlowLevelSequenceActor.cpp +++ b/Source/Flow/Private/LevelSequence/FlowLevelSequenceActor.cpp @@ -1,10 +1,10 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "LevelSequence/FlowLevelSequenceActor.h" +#include "FlowLogChannels.h" #include "LevelSequence/FlowLevelSequencePlayer.h" #include "Net/UnrealNetwork.h" #include "Runtime/Launch/Resources/Version.h" - #include "DefaultLevelSequenceInstanceData.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowLevelSequenceActor) @@ -20,6 +20,7 @@ void AFlowLevelSequenceActor::GetLifetimeReplicatedProps(TArray& OutActor ) { if (LevelSequence == nullptr) diff --git a/Source/Flow/Private/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.cpp b/Source/Flow/Private/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.cpp new file mode 100644 index 00000000..8942d1dc --- /dev/null +++ b/Source/Flow/Private/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.cpp @@ -0,0 +1,12 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h" + +#include "AddOns/FlowNodeAddOn.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(IFlowPlayLevelSequenceAddOnInterface) + +bool IFlowPlayLevelSequenceAddOnInterface::ImplementsInterfaceSafe(const UFlowNodeAddOn* AddOnTemplate) +{ + return IsValid(AddOnTemplate) && AddOnTemplate->Implements(); +} diff --git a/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp b/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp index 3cd94789..e07b68a3 100644 --- a/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp +++ b/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp @@ -5,7 +5,10 @@ #include "FlowAsset.h" #include "FlowLogChannels.h" #include "FlowSubsystem.h" +#include "AddOns/FlowNodeAddOn.h" +#include "LevelSequence/FlowLevelSequenceActor.h" #include "LevelSequence/FlowLevelSequencePlayer.h" +#include "LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h" #if WITH_EDITOR #include "MovieScene/MovieSceneFlowTrack.h" @@ -13,7 +16,6 @@ #endif #include "LevelSequence.h" -#include "LevelSequenceActor.h" #include "VisualLogger/VisualLogger.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_PlayLevelSequence) @@ -148,17 +150,29 @@ void UFlowNode_PlayLevelSequence::InitializeInstance() { Super::InitializeInstance(); + SequenceActor = nullptr; + // Cache Play Rate set by user CachedPlayRate = PlaybackSettings.PlayRate; } +EFlowAddOnAcceptResult UFlowNode_PlayLevelSequence::AcceptFlowNodeAddOnChild_Implementation( + const UFlowNodeAddOn* AddOnTemplate, + const TArray& AdditionalAddOnsToAssumeAreChildren) const +{ + if (IFlowPlayLevelSequenceAddOnInterface::ImplementsInterfaceSafe(AddOnTemplate)) + { + return EFlowAddOnAcceptResult::TentativeAccept; + } + + return Super::AcceptFlowNodeAddOnChild_Implementation(AddOnTemplate, AdditionalAddOnsToAssumeAreChildren); +} + void UFlowNode_PlayLevelSequence::CreatePlayer() { LoadedSequence = Sequence.LoadSynchronous(); if (LoadedSequence) { - ALevelSequenceActor* SequenceActor; - AActor* OwningActor = TryGetRootFlowActorOwner(); // Apply AActor::CustomTimeDilation from owner of the Root Flow @@ -178,6 +192,17 @@ void UFlowNode_PlayLevelSequence::CreatePlayer() SequencePlayer->SetFlowEventReceiver(this); } + // Notify add-ons so they can apply binding overrides before Play() is called + if (AFlowLevelSequenceActor* FlowSequenceActor = SequenceActor.Get()) + { + ForEachAddOnForClass([this, FlowSequenceActor, OwningActor](UFlowNodeAddOn& AddOn) + { + IFlowPlayLevelSequenceAddOnInterface* Interface = CastChecked(&AddOn); + Interface->OnSequencePlayerCreated(*FlowSequenceActor, OwningActor); + return EFlowForEachAddOnFunctionReturnValue::Continue; + }); + } + const FFrameRate FrameRate = LoadedSequence->GetMovieScene()->GetTickResolution(); const FFrameNumber PlaybackStartFrame = LoadedSequence->GetMovieScene()->GetPlaybackRange().GetLowerBoundValue(); StartTime = FQualifiedFrameTime(FFrameTime(PlaybackStartFrame, 0.0f), FrameRate).AsSeconds(); @@ -319,6 +344,12 @@ void UFlowNode_PlayLevelSequence::Cleanup() SequencePlayer = nullptr; } + if (IsValid(SequenceActor) && SequenceActor->HasAuthority()) + { + SequenceActor->Destroy(); + } + SequenceActor = nullptr; + LoadedSequence = nullptr; StartTime = 0.0f; ElapsedTime = 0.0f; diff --git a/Source/Flow/Private/Nodes/FlowNodeBase.cpp b/Source/Flow/Private/Nodes/FlowNodeBase.cpp index 04aa5364..f9d0de3a 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -621,7 +621,7 @@ FString UFlowNodeBase::GetNodeCategory() const } } - return Category; + return K2_GetNodeCategory(); } bool UFlowNodeBase::GetDynamicTitleColor(FLinearColor& OutColor) const @@ -636,6 +636,32 @@ bool UFlowNodeBase::GetDynamicTitleColor(FLinearColor& OutColor) const return false; } +FText UFlowNodeBase::GetNodeTitle() const +{ + if (HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject)) + { + // For the archetype of the node (e.g. in the node selection UI), only use the default value + return UFlowNodeBase::K2_GetNodeTitle_Implementation(); + } + else + { + return K2_GetNodeTitle(); + } +} + +FText UFlowNodeBase::GetNodeToolTip() const +{ + if (HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject)) + { + // For the archetype of the node (e.g. in the node selection UI), only use the default value + return UFlowNodeBase::K2_GetNodeToolTip_Implementation(); + } + else + { + return K2_GetNodeToolTip(); + } +} + FText UFlowNodeBase::GetGeneratedDisplayName() const { static const FName NAME_GeneratedDisplayName(TEXT("GeneratedDisplayName")); @@ -820,6 +846,15 @@ FText UFlowNodeBase::K2_GetNodeToolTip_Implementation() const #endif } +FString UFlowNodeBase::K2_GetNodeCategory_Implementation() const +{ +#if WITH_EDITORONLY_DATA + return Category; +#else + return ""; +#endif +} + FText UFlowNodeBase::GetNodeConfigText() const { #if WITH_EDITORONLY_DATA diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_Finish.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_Finish.cpp index ea34286d..1afd1297 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_Finish.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_Finish.cpp @@ -2,21 +2,19 @@ #include "Nodes/Graph/FlowNode_Finish.h" +#include "FlowAsset.h" + #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_Finish) UFlowNode_Finish::UFlowNode_Finish() { -#if WITH_EDITOR - Category = TEXT("Graph"); - NodeDisplayStyle = FlowNodeStyle::InOut; -#endif - OutputPins = {}; - AllowedSignalModes = {EFlowSignalMode::Enabled, EFlowSignalMode::Disabled}; } void UFlowNode_Finish::ExecuteInput(const FName& PinName) { + CommitOutputDataPinValues(); + // this will call FinishFlow() Finish(); } diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_SetGraphOutput.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_SetGraphOutput.cpp new file mode 100644 index 00000000..0753dd86 --- /dev/null +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_SetGraphOutput.cpp @@ -0,0 +1,113 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Nodes/Graph/FlowNode_SetGraphOutput.h" + +#include "FlowAsset.h" +#include "Types/FlowDataPinResults.h" +#include "Types/FlowNamedDataPinProperty.h" + +#if WITH_EDITOR +#include "Types/FlowAutoDataPinsWorkingData.h" +#endif + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_SetGraphOutput) + +UFlowNode_SetGraphOutput::UFlowNode_SetGraphOutput() +{ +#if WITH_EDITOR + Category = TEXT("Graph"); + NodeDisplayStyle = FlowNodeStyle::InOut; +#endif + + AllowedSignalModes = {EFlowSignalMode::Enabled, EFlowSignalMode::Disabled}; +} + +void UFlowNode_SetGraphOutput::ExecuteInput(const FName& PinName) +{ + CommitOutputDataPinValues(); + + TriggerFirstOutput(true); +} + +void UFlowNode_SetGraphOutput::CommitOutputDataPinValues() +{ + UFlowAsset* FlowAsset = GetFlowAsset(); + if (!IsValid(FlowAsset)) + { + return; + } + + const UFlowAsset* TemplateAsset = FlowAsset->GetTemplateAsset(); + if (!IsValid(TemplateAsset)) + { + return; + } + + bool bNeedsFlush = false; + + for (const FFlowNamedDataPinProperty& Declaration : TemplateAsset->GetOutputDataPinDeclarations()) + { + if (!Declaration.IsValid()) + { + continue; + } + + if (!IsInputConnected(Declaration.Name)) + { + continue; + } + + const FFlowDataPinResult PinResult = TryResolveDataPin_SetGraphOutputAccess(Declaration.Name); + if (PinResult.Result == EFlowDataPinResolveResult::Success) + { + bNeedsFlush = true; + + FlowAsset->WriteOutputDataPinValue(Declaration.Name, PinResult.ResultValue); + } + } + + if (bNeedsFlush) + { + FlowAsset->FlushOutputDataPinValuesToReceiver(); + } +} + +#if WITH_EDITOR +bool UFlowNode_SetGraphOutput::SupportsContextPins() const +{ + const UFlowAsset* FlowAsset = GetFlowAsset(); + if (IsValid(FlowAsset) && !FlowAsset->GetOutputDataPinDeclarations().IsEmpty()) + { + return true; + } + + return Super::SupportsContextPins(); +} + +void UFlowNode_SetGraphOutput::AutoGenerateDataPins(FFlowDataPinValueOwner& ValueOwner, FFlowAutoDataPinsWorkingData& InOutWorkingData) +{ + Super::AutoGenerateDataPins(ValueOwner, InOutWorkingData); + + const UFlowAsset* FlowAsset = GetFlowAsset(); + if (!IsValid(FlowAsset)) + { + return; + } + + for (FFlowNamedDataPinProperty DeclarationCopy : FlowAsset->GetOutputDataPinDeclarations()) + { + if (!DeclarationCopy.IsValid()) + { + continue; + } + + if (DeclarationCopy.DataPinValue.IsValid()) + { + FFlowDataPinValue& Value = DeclarationCopy.DataPinValue.GetMutable(); + Value.bIsInputPin = true; + } + + DeclarationCopy.AutoGenerateDataPinForProperty(ValueOwner, InOutWorkingData); + } +} +#endif diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp index 3c07698f..ac268cce 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp @@ -97,14 +97,26 @@ void UFlowNode_SubGraph::ExecuteInput(const FName& PinName) void UFlowNode_SubGraph::Cleanup() { - if (CanBeAssetInstanced() && GetFlowSubsystem()) + UFlowSubsystem* FlowSubsystem = GetFlowSubsystem(); + if (CanBeAssetInstanced() && FlowSubsystem) { - GetFlowSubsystem()->RemoveSubFlow(this, EFlowFinishPolicy::Keep); + FlowSubsystem->FinishSubFlow(this, EFlowFinishPolicy::Keep); } Super::Cleanup(); } +void UFlowNode_SubGraph::DeinitializeInstance() +{ + UFlowSubsystem* FlowSubsystem = GetFlowSubsystem(); + if (CanBeAssetInstanced() && FlowSubsystem) + { + FlowSubsystem->RemoveSubFlow(this, EFlowFinishPolicy::Keep); + } + + Super::DeinitializeInstance(); +} + void UFlowNode_SubGraph::ForceFinishNode() { TriggerFirstOutput(true); @@ -119,6 +131,11 @@ void UFlowNode_SubGraph::OnLoad_Implementation() } } +void UFlowNode_SubGraph::ReceiveOutputDataSnapshot(const FFlowOutputDataPinValues& Snapshot) +{ + CachedOutputDataPinValues = Snapshot; +} + #if WITH_EDITOR FText UFlowNode_SubGraph::K2_GetNodeTitle_Implementation() const @@ -236,6 +253,22 @@ void UFlowNode_SubGraph::AutoGenerateDataPins(FFlowDataPinValueOwner& ValueOwner } } } + + for (FFlowNamedDataPinProperty Declaration : Asset->GetOutputDataPinDeclarations()) + { + if (!Declaration.IsValid()) + { + continue; + } + + if (Declaration.DataPinValue.IsValid()) + { + FFlowDataPinValue& Value = Declaration.DataPinValue.GetMutable(); + Value.bIsInputPin = false; + } + + Declaration.AutoGenerateDataPinForProperty(ValueOwner, InOutWorkingData); + } } FFlowDataPinResult UFlowNode_SubGraph::TrySupplyDataPin(FName PinName) const @@ -248,6 +281,19 @@ FFlowDataPinResult UFlowNode_SubGraph::TrySupplyDataPin(FName PinName) const return Super::TrySupplyDataPin(PinName); } + // Check cached output data pin values first — output pins are never "input connected", + // so this must come before IsInputConnected to avoid a spurious "unknown input pin" error. + if (const TInstancedStruct* CachedValue = CachedOutputDataPinValues.Values.Find(PinName)) + { + if (CachedValue->IsValid()) + { + FFlowDataPinResult Result; + Result.Result = EFlowDataPinResolveResult::Success; + Result.ResultValue = *CachedValue; + return Result; + } + } + if (!IsInputConnected(PinName)) { const bool bHasAssetParams = IsInputConnected(AssetParams_MemberName) || !AssetParams.IsNull(); diff --git a/Source/Flow/Private/Policies/FlowPreloadPolicy.cpp b/Source/Flow/Private/Policies/FlowPreloadPolicy.cpp new file mode 100644 index 00000000..18b8e61b --- /dev/null +++ b/Source/Flow/Private/Policies/FlowPreloadPolicy.cpp @@ -0,0 +1,5 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Policies/FlowPreloadPolicy.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowPreloadPolicy) diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index fa1048b0..adec0a40 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -5,7 +5,11 @@ #include "FlowTypes.h" #include "Asset/FlowAssetParamsTypes.h" #include "Asset/FlowDeferredTransitionScope.h" +#include "Interfaces/FlowGraphOutputDataReceiverInterface.h" #include "Nodes/FlowNode.h" +#include "Types/FlowDataPinValue.h" +#include "Types/FlowNamedDataPinProperty.h" +#include "Types/FlowOutputDataPinValues.h" #if WITH_EDITOR #include "FlowMessageLog.h" @@ -26,6 +30,8 @@ struct FFlowPinConnectionPolicy; class UEdGraph; class UEdGraphNode; +class UFlowAsset; +class UFlowAssetParams; #if !UE_BUILD_SHIPPING DECLARE_DELEGATE(FFlowGraphEvent); @@ -49,6 +55,7 @@ class FLOW_API UFlowAsset : public UObject friend class FFlowAssetDetails; friend class FFlowNode_SubGraphDetails; friend class UFlowGraphSchema; + friend struct FFlowDeferredTransitionScope; UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Flow Asset") FGuid AssetGuid; @@ -131,6 +138,15 @@ class FLOW_API UFlowAsset : public UObject UPROPERTY() TMap> Nodes; +public: + const TArray& GetOutputDataPinDeclarations() const { return OutputDataPinDeclarations; } + +protected: + /* Output Data Pins define typed data values that this graph produces when it finishes. + * Sub Graph node using this Flow Asset will generate a context Output Data Pin for every entry on this list. */ + UPROPERTY(EditAnywhere, Category = "Sub Graph") + TArray OutputDataPinDeclarations; + public: #if WITH_EDITOR FFlowGraphEvent OnSubGraphReconstructionRequested; @@ -357,11 +373,23 @@ class FLOW_API UFlowAsset : public UObject UPROPERTY(Transient) EFlowFinishPolicy FinishPolicy; + /* Receiver that will be given a snapshot of OutputDataPinValues when this graph finishes. + * Typically the SubGraph node that created this instance. */ + UPROPERTY(Transient) + TWeakObjectPtr OutputDataReceiver; + + /* Live output data pin values for this running instance. + * Initialized from OutputDataPinDeclarations defaults at StartFlow; updated by SetGraphOutput/Finish nodes. */ + UPROPERTY(Transient) + FFlowOutputDataPinValues OutputDataPinValues; + public: virtual void InitializeInstance(const TWeakObjectPtr InOwner, UFlowAsset& InTemplateAsset); virtual void DeinitializeInstance(); bool IsInstanceInitialized() const { return IsValid(TemplateAsset); } + void FinishFlowAndDeinitializeInstance(const EFlowFinishPolicy InFinishPolicy); + UFlowAsset* GetTemplateAsset() const { return TemplateAsset; } /* Object that spawned Root Flow instance, i.e. World Settings or Player Controller. @@ -380,18 +408,28 @@ class FLOW_API UFlowAsset : public UObject AActor* TryFindActorOwner() const; virtual void PreStartFlow(); - virtual void StartFlow(IFlowDataPinValueSupplierInterface* DataPinValueSupplier = nullptr); + virtual void StartFlow(IFlowDataPinValueSupplierInterface* DataPinValueSupplier = nullptr, IFlowGraphOutputDataReceiverInterface* InOutputDataReceiver = nullptr); + + /* Write a single output data pin value into the live store for this running instance. + * Called by SetGraphOutput and Finish nodes for each connected output pin. */ + void WriteOutputDataPinValue(const FName& PinName, const TInstancedStruct& Value); + + /* Flush all of the OutputDataPinValues to the receiver (if set) */ + void FlushOutputDataPinValuesToReceiver(); + + virtual void FinishFlow(const EFlowFinishPolicy InFinishPolicy); + bool HasStartedFlow() const; protected: virtual void FinishNode(UFlowNode* Node); void ResetNodes(); - -public: - virtual void FinishFlow(const EFlowFinishPolicy InFinishPolicy, const bool bRemoveInstance = true); + + void InitializeOutputDataReceiverAndValues(IFlowGraphOutputDataReceiverInterface* InOutputDataReceiver); public: UFlowSubsystem* GetFlowSubsystem() const; + FName GetDisplayName() const; UFlowNode_SubGraph* GetNodeOwningThisAssetInstance() const; UFlowAsset* GetParentInstance() const; diff --git a/Source/Flow/Public/FlowSubsystem.h b/Source/Flow/Public/FlowSubsystem.h index ea68d892..5cf33058 100644 --- a/Source/Flow/Public/FlowSubsystem.h +++ b/Source/Flow/Public/FlowSubsystem.h @@ -78,16 +78,21 @@ class FLOW_API UFlowSubsystem : public UGameInstanceSubsystem * 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 */ UFUNCTION(BlueprintCallable, Category = "FlowSubsystem", meta = (DefaultToSelf = "Owner")) - virtual void FinishRootFlow(UObject* Owner, UFlowAsset* TemplateAsset, const EFlowFinishPolicy FinishPolicy); + virtual void FinishAndDeinitializeRootFlow(UObject* Owner, UFlowAsset* TemplateAsset, const EFlowFinishPolicy FinishPolicy); /* 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 */ UFUNCTION(BlueprintCallable, Category = "FlowSubsystem", meta = (DefaultToSelf = "Owner")) - virtual void FinishAllRootFlows(UObject* Owner, const EFlowFinishPolicy FinishPolicy); + virtual void FinishAndDeinitializeAllRootFlows(UObject* Owner, const EFlowFinishPolicy FinishPolicy); protected: UFlowAsset* CreateSubFlow(UFlowNode_SubGraph* SubGraphNode, const FString& SavedInstanceName = FString(), const bool bPreloading = false); + + /* Finishes the SubFlow running in the SubGraphNode. It does not deinitialize or removes from the internal InstancedSubFlows list */ + void FinishSubFlow(UFlowNode_SubGraph* SubGraphNode, const EFlowFinishPolicy FinishPolicy); + + /* Removes the Subflow from the InstancedSubFlows list; and Finishes and Deinitializes it. */ void RemoveSubFlow(UFlowNode_SubGraph* SubGraphNode, const EFlowFinishPolicy FinishPolicy); public: diff --git a/Source/Flow/Public/Interfaces/FlowGraphOutputDataReceiverInterface.h b/Source/Flow/Public/Interfaces/FlowGraphOutputDataReceiverInterface.h new file mode 100644 index 00000000..d58e05d9 --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowGraphOutputDataReceiverInterface.h @@ -0,0 +1,27 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +#pragma once + +#include "UObject/Interface.h" + +#include "FlowGraphOutputDataReceiverInterface.generated.h" + +struct FFlowOutputDataPinValues; + +/** + * Interface for objects that receive a snapshot of a Flow Asset's output data pin values + * when the asset's graph finishes execution. + * Example: UFlowNode_SubGraph receives the snapshot and caches it for downstream pin resolution. + */ +UINTERFACE(MinimalAPI, NotBlueprintable, DisplayName = "Flow Graph Output Data Receiver Interface") +class UFlowGraphOutputDataReceiverInterface : public UInterface +{ + GENERATED_BODY() +}; + +class FLOW_API IFlowGraphOutputDataReceiverInterface +{ + GENERATED_BODY() + +public: + virtual void ReceiveOutputDataSnapshot(const FFlowOutputDataPinValues& OutputDataPinValues) { } +}; diff --git a/Source/Flow/Public/LevelSequence/FlowLevelSequenceActor.h b/Source/Flow/Public/LevelSequence/FlowLevelSequenceActor.h index 99c6d5cf..6144b52a 100644 --- a/Source/Flow/Public/LevelSequence/FlowLevelSequenceActor.h +++ b/Source/Flow/Public/LevelSequence/FlowLevelSequenceActor.h @@ -6,6 +6,19 @@ class ULevelSequence; +/** Single actor binding override entry, replicated from server to clients. */ +USTRUCT() +struct FFlowSequenceBindingEntry +{ + GENERATED_BODY() + + UPROPERTY() + FName BindingTag; + + UPROPERTY() + TObjectPtr BoundActor; +}; + /** * Custom ALevelSequenceActor is needed to override ULevelSequencePlayer class. */ @@ -24,7 +37,19 @@ class FLOW_API AFlowLevelSequenceActor : public ALevelSequenceActor void SetPlaybackSettings(FMovieSceneSequencePlaybackSettings NewPlaybackSettings); void SetReplicatedLevelSequenceAsset(ULevelSequence* Asset); + /** Server only. Adds a binding override that replicates to clients via OnRep_BindingEntries. */ + void AddBinding(FName Tag, AActor* Actor); + + /** Server only. Clears all binding overrides and replicates the cleared state to clients. */ + void ClearAllBindings(); + protected: UFUNCTION() void OnRep_ReplicatedLevelSequenceAsset(); + + UPROPERTY(ReplicatedUsing = OnRep_BindingEntries) + TArray BindingEntries; + + UFUNCTION() + void OnRep_BindingEntries(); }; diff --git a/Source/Flow/Public/LevelSequence/FlowLevelSequencePlayer.h b/Source/Flow/Public/LevelSequence/FlowLevelSequencePlayer.h index 038125d1..525b6754 100644 --- a/Source/Flow/Public/LevelSequence/FlowLevelSequencePlayer.h +++ b/Source/Flow/Public/LevelSequence/FlowLevelSequencePlayer.h @@ -4,6 +4,7 @@ #include "LevelSequencePlayer.h" #include "FlowLevelSequencePlayer.generated.h" +class AFlowLevelSequenceActor; class UFlowNode; /** @@ -29,7 +30,7 @@ class FLOW_API UFlowLevelSequencePlayer : public ULevelSequencePlayer AActor* TransformOriginActor, const bool bReplicates, const bool bAlwaysRelevant, - ALevelSequenceActor*& OutActor); + TObjectPtr& OutActor); void SetFlowEventReceiver(UFlowNode* FlowNode) { FlowEventReceiver = FlowNode; } diff --git a/Source/Flow/Public/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h b/Source/Flow/Public/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h new file mode 100644 index 00000000..d98c8272 --- /dev/null +++ b/Source/Flow/Public/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h @@ -0,0 +1,40 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.h" + +#include "IFlowPlayLevelSequenceAddOnInterface.generated.h" + +class AActor; +class AFlowLevelSequenceActor; +class UFlowNodeAddOn; + +/** + * Interface for add-ons that want to apply setup (e.g. actor binding overrides) to a + * level sequence actor immediately after the sequence player is created, before Play() is called. + * + * Attach to any flow node that spawns an AFlowLevelSequenceActor and supports this interface. + */ +UINTERFACE(MinimalAPI) +class UFlowPlayLevelSequenceAddOnInterface : public UInterface +{ + GENERATED_BODY() +}; + +class FLOW_API IFlowPlayLevelSequenceAddOnInterface +{ + GENERATED_BODY() + +public: + + static bool ImplementsInterfaceSafe(const UFlowNodeAddOn* AddOnTemplate); + + /** + * Called after the sequence player is created, before Play() is invoked. + * + * @param SequenceActor The spawned AFlowLevelSequenceActor. Never null when called. + * @param FlowOwner The actor that owns the flow graph. May be null. + */ + virtual void OnSequencePlayerCreated(AFlowLevelSequenceActor& SequenceActor, AActor* FlowOwner) {} +}; diff --git a/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h b/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h index 02eaa1b5..19914c08 100644 --- a/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h +++ b/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h @@ -10,6 +10,7 @@ #include "Nodes/FlowNode.h" #include "FlowNode_PlayLevelSequence.generated.h" +class AFlowLevelSequenceActor; class UFlowLevelSequencePlayer; DECLARE_MULTICAST_DELEGATE(FFlowNodeLevelSequenceEvent); @@ -75,6 +76,9 @@ class FLOW_API UFlowNode_PlayLevelSequence UPROPERTY() TObjectPtr SequencePlayer; + UPROPERTY() + TObjectPtr SequenceActor; + /* Play Rate set by the user in PlaybackSettings. */ float CachedPlayRate; @@ -106,6 +110,10 @@ class FLOW_API UFlowNode_PlayLevelSequence virtual void FlushContent() override; // -- + // UFlowNodeBase + virtual EFlowAddOnAcceptResult AcceptFlowNodeAddOnChild_Implementation(const UFlowNodeAddOn* AddOnTemplate, const TArray& AdditionalAddOnsToAssumeAreChildren) const override; + // -- + virtual void InitializeInstance() override; void CreatePlayer(); diff --git a/Source/Flow/Public/Nodes/FlowNodeBase.h b/Source/Flow/Public/Nodes/FlowNodeBase.h index c3c1a4d0..2981fefc 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -288,6 +288,12 @@ class FLOW_API UFlowNodeBase UFUNCTION(BlueprintPure, Category = DataPins, DisplayName = "Resolve DataPin By Name") FFlowDataPinResult TryResolveDataPin(FName PinName) const; +protected: + /* Protected accessor for TryResolveDataPin()'s use + * (we still want "most" flow nodes to not use TryResolveDataPin directly, + * they should be using the template versions below.) */ + FFlowDataPinResult TryResolveDataPin_SetGraphOutputAccess(FName PinName) const { return TryResolveDataPin(PinName); } + public: /* Generic single-value resolve & extractor. */ template @@ -456,8 +462,8 @@ class FLOW_API UFlowNodeBase /* This method allows to have different for every node instance, i.e. Red if node represents enemy, Green if node represents a friend. */ virtual bool GetDynamicTitleColor(FLinearColor& OutColor) const; - virtual FText GetNodeTitle() const { return K2_GetNodeTitle(); } - virtual FText GetNodeToolTip() const { return K2_GetNodeToolTip(); } + virtual FText GetNodeTitle() const; + virtual FText GetNodeToolTip() const; FText GetGeneratedDisplayName() const; @@ -488,7 +494,10 @@ class FLOW_API UFlowNodeBase UFUNCTION(BlueprintNativeEvent, Category = "FlowNode") FText K2_GetNodeToolTip() const; - + + UFUNCTION(BlueprintNativeEvent, Category = "FlowNode") + FString K2_GetNodeCategory() const; + UFUNCTION(BlueprintPure, Category = "FlowNode") virtual FText GetNodeConfigText() const; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_Finish.h b/Source/Flow/Public/Nodes/Graph/FlowNode_Finish.h index b4458eba..1717aa3c 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_Finish.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_Finish.h @@ -1,7 +1,7 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #pragma once -#include "Nodes/FlowNode.h" +#include "Nodes/Graph/FlowNode_SetGraphOutput.h" #include "FlowNode_Finish.generated.h" /** @@ -9,7 +9,7 @@ * All active nodes and sub graphs will be deactivated. */ UCLASS(NotBlueprintable, meta = (DisplayName = "Finish")) -class FLOW_API UFlowNode_Finish : public UFlowNode +class FLOW_API UFlowNode_Finish : public UFlowNode_SetGraphOutput { GENERATED_BODY() diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_SetGraphOutput.h b/Source/Flow/Public/Nodes/Graph/FlowNode_SetGraphOutput.h new file mode 100644 index 00000000..f3b89ef6 --- /dev/null +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_SetGraphOutput.h @@ -0,0 +1,35 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +#pragma once + +#include "Nodes/FlowNode.h" +#include "FlowNode_SetGraphOutput.generated.h" + +/** + * Resolves connected input data pins from the Flow Asset's OutputDataPinDeclarations and + * writes their values to the asset's output data store. Functions as a pass-through exec node. + * UFlowNode_Finish derives from this class and additionally finishes the graph. + */ +UCLASS(NotBlueprintable, meta = (DisplayName = "Set Graph Output")) +class FLOW_API UFlowNode_SetGraphOutput : public UFlowNode +{ + GENERATED_BODY() + +public: + UFlowNode_SetGraphOutput(); + +protected: + virtual void ExecuteInput(const FName& PinName) override; + + /* Resolve all connected input data pins and write them to the Flow Asset's output store. */ + void CommitOutputDataPinValues(); + +#if WITH_EDITOR + // IFlowContextPinSupplierInterface + virtual bool SupportsContextPins() const override; + // -- + + // IFlowDataPinValueOwnerInterface + virtual void AutoGenerateDataPins(FFlowDataPinValueOwner& ValueOwner, FFlowAutoDataPinsWorkingData& InOutWorkingData) override; + // -- +#endif +}; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h index 5d7dfaf6..cf434b59 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h @@ -1,8 +1,12 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #pragma once +#include "Interfaces/FlowGraphOutputDataReceiverInterface.h" #include "Interfaces/FlowPreloadableInterface.h" #include "Nodes/FlowNode.h" +#include "Types/FlowDataPinValue.h" +#include "Types/FlowOutputDataPinValues.h" +#include "StructUtils/InstancedStruct.h" #include "FlowNode_SubGraph.generated.h" class UFlowAssetParams; @@ -14,11 +18,12 @@ UCLASS(NotBlueprintable, meta = (DisplayName = "Sub Graph")) class FLOW_API UFlowNode_SubGraph : public UFlowNode , public IFlowPreloadableInterface + , public IFlowGraphOutputDataReceiverInterface { GENERATED_BODY() public: - UFlowNode_SubGraph(); + UFlowNode_SubGraph(); friend class UFlowAsset; friend class FFlowNode_SubGraphDetails; @@ -27,7 +32,7 @@ class FLOW_API UFlowNode_SubGraph static FFlowPin StartPin; static FFlowPin FinishPin; -private: +protected: UPROPERTY(EditAnywhere, Category = "Graph") TSoftObjectPtr Asset; @@ -43,7 +48,17 @@ class FLOW_API UFlowNode_SubGraph UPROPERTY(SaveGame) FString SavedAssetInstanceName; + /* Cached output data pin values received from the inner Flow Asset when it finishes. + * Note - Not saved "yet", but should decide if we should include the + * cached output values in the save state. */ + UPROPERTY(Transient) + FFlowOutputDataPinValues CachedOutputDataPinValues; + protected: + // IFlowGraphOutputDataReceiverInterface + virtual void ReceiveOutputDataSnapshot(const FFlowOutputDataPinValues& Snapshot) override; + // -- + virtual bool CanBeAssetInstanced() const; // IFlowPreloadableInterface @@ -53,6 +68,7 @@ class FLOW_API UFlowNode_SubGraph virtual void ExecuteInput(const FName& PinName) override; virtual void Cleanup() override; + virtual void DeinitializeInstance() override; public: virtual void ForceFinishNode() override; diff --git a/Source/Flow/Public/Types/FlowOutputDataPinValues.h b/Source/Flow/Public/Types/FlowOutputDataPinValues.h new file mode 100644 index 00000000..6d587ac2 --- /dev/null +++ b/Source/Flow/Public/Types/FlowOutputDataPinValues.h @@ -0,0 +1,25 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +#pragma once + +#include "UObject/NameTypes.h" +#include "StructUtils/InstancedStruct.h" +#include "Containers/Map.h" + +#include "FlowOutputDataPinValues.generated.h" + +struct FFlowDataPinValue; + +/** + * Container for output data pin values from a Flow Asset execution. + * Holds a map of pin names to their resolved values. + */ +USTRUCT() +struct FFlowOutputDataPinValues +{ + GENERATED_BODY() + +public: + /* Map of output pin names to their resolved values. */ + UPROPERTY() + TMap> Values; +}; diff --git a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp index af262a4e..860aeabe 100644 --- a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp +++ b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp @@ -242,7 +242,7 @@ FText SFlowAssetInstanceList::JoinInstanceAndContextTexts(const FObjectKey& Asse { if (const UFlowAsset* Instance = Cast(AssetInstance.ResolveObjectPtr())) { - FText Result = FText::FromName(Instance->GetFName()); + FText Result = FText::FromName(Instance->GetDisplayName()); // add context name if there are multiple contexts present if (InstancesPerContext.Num() > 1) @@ -321,7 +321,7 @@ void SFlowAssetBreadcrumb::FillBreadcrumb() const TWeakObjectPtr Instance = InstancesFromRoot[Index]; TWeakObjectPtr ChildInstance = Index < InstancesFromRoot.Num() - 1 ? InstancesFromRoot[Index + 1] : nullptr; - BreadcrumbTrail->PushCrumb(FText::FromName(Instance->GetFName()), FFlowBreadcrumb(Instance, ChildInstance)); + BreadcrumbTrail->PushCrumb(FText::FromName(Instance->GetDisplayName()), FFlowBreadcrumb(Instance, ChildInstance)); } } } diff --git a/docs/.gitattributes b/docs/.gitattributes deleted file mode 100644 index d18bf85f..00000000 --- a/docs/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Enforce LF line endings for all text files -* text=auto eol=lf diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 0d010b15..00000000 --- a/docs/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -_site/ -.jekyll-cache/ -.jekyll-metadata -Gemfile.lock