diff --git a/Config/DefaultFlow.ini b/Config/DefaultFlow.ini index 0af853cb6..2429a3b71 100644 --- a/Config/DefaultFlow.ini +++ b/Config/DefaultFlow.ini @@ -2,3 +2,5 @@ +ClassRedirects=(OldName="/Script/Flow.FlowNode_CustomEvent",NewName="/Script/Flow.FlowNode_CustomInput") +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/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 16a6570e3..762ede881 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -18,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" @@ -914,7 +915,7 @@ void UFlowAsset::ClearInstances() { if (ActiveInstances.IsValidIndex(i) && ActiveInstances[i]) { - ActiveInstances[i]->FinishFlow(EFlowFinishPolicy::Keep); + ActiveInstances[i]->FinishFlowAndDeinitializeInstance(EFlowFinishPolicy::Keep); } } @@ -1046,6 +1047,12 @@ AActor* UFlowAsset::TryFindActorOwner() const return nullptr; } +void UFlowAsset::FinishFlowAndDeinitializeInstance(const EFlowFinishPolicy InFinishPolicy) +{ + FinishFlow(InFinishPolicy); + DeinitializeInstance(); +} + void UFlowAsset::PreStartFlow() { ResetNodes(); @@ -1155,13 +1162,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; } @@ -1182,7 +1190,7 @@ void UFlowAsset::ResetNodes() RecordedNodes.Empty(); } -void UFlowAsset::FinishFlow(const EFlowFinishPolicy InFinishPolicy, const bool bRemoveInstance /*= true*/) +void UFlowAsset::FinishFlow(const EFlowFinishPolicy InFinishPolicy) { FinishPolicy = InFinishPolicy; @@ -1193,13 +1201,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 @@ -1248,6 +1251,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 da1dbe1f6..62ca1dbc3 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 290266a63..98f7bd2fa 100644 --- a/Source/Flow/Private/FlowSubsystem.cpp +++ b/Source/Flow/Private/FlowSubsystem.cpp @@ -127,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; @@ -143,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; @@ -162,7 +162,7 @@ void UFlowSubsystem::FinishAllRootFlows(UObject* Owner, const EFlowFinishPolicy for (UFlowAsset* InstanceToFinish : InstancesToFinish) { RootInstances.Remove(InstanceToFinish); - InstanceToFinish->FinishFlow(FinishPolicy); + InstanceToFinish->FinishFlowAndDeinitializeInstance(FinishPolicy); } } @@ -205,6 +205,23 @@ UFlowAsset* UFlowSubsystem::CreateSubFlow(UFlowNode_SubGraph* SubGraphNode, cons return AssetInstance; } +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)) @@ -214,7 +231,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/Nodes/Graph/FlowNode_SubGraph.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp index 0444d83bb..dd8c6f374 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp @@ -100,14 +100,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); @@ -178,6 +190,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 diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index a621a75e9..0b765bb1a 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -138,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; @@ -388,6 +397,8 @@ class FLOW_API UFlowAsset : public UObject virtual FName GetInstanceName() const; + void FinishFlowAndDeinitializeInstance(const EFlowFinishPolicy InFinishPolicy); + UFlowAsset* GetTemplateAsset() const { return TemplateAsset; } /* Object that spawned Root Flow instance, i.e. World Settings or Player Controller. @@ -425,6 +436,8 @@ class FLOW_API UFlowAsset : public UObject virtual void FinishNode(UFlowNode* Node); void ResetNodes(); + void InitializeOutputDataReceiverAndValues(IFlowGraphOutputDataReceiverInterface* InOutputDataReceiver); + public: virtual void FinishFlow(const EFlowFinishPolicy InFinishPolicy, const bool bRemoveInstance = true); diff --git a/Source/Flow/Public/FlowSubsystem.h b/Source/Flow/Public/FlowSubsystem.h index ea68d8921..5cf330581 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/Nodes/Graph/FlowNode_SubGraph.h b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h index f0be3069b..1cd913fdf 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h @@ -53,6 +53,10 @@ class FLOW_API UFlowNode_SubGraph FFlowOutputDataPinValues CachedOutputDataPinValues; protected: + // IFlowGraphOutputDataReceiverInterface + virtual void ReceiveOutputDataSnapshot(const FFlowOutputDataPinValues& Snapshot) override; + // -- + virtual bool CanBeAssetInstanced() const; public: @@ -63,6 +67,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;