What is the recommended approach for creating a DataFlowNode that reacts to Dataflow Instance Variables changing?

Hi there,

This is a question that blocks the implementation stage of a current project feature, however I am on vacation until Wednesday 1st October anyway, so don’t need a response until then at the earliest.

I have created:

FMyDataflowNode : public FDataflowNode

It performs a simplified version of the functionality found in FGetDataflowVariableNode:

void FMyDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
  TSubclassOf<AActor> Dataflow_ActorClassVariable;
  
  if (const UE::Dataflow::FEngineContext* EngineContext = Context.AsType<UE::Dataflow::FEngineContext>())
  {
      if (IDataflowInstanceInterface* Interface = Cast<IDataflowInstanceInterface>(EngineContext->Owner))
      {
         const FDataflowInstance& DataflowInstance = Interface->GetDataflowInstance();
         if (DataflowInstance.GetVariableOverrides().IsVariableOverridden(VariableName))
         {
            TValueOrError<UClass*, EPropertyBagResult> Result = DataflowInstance.GetVariableOverrides().GetVariables().GetValueClass(VariableName);
            if (Result.HasValue())
            {
               Dataflow_ActorClassVariable = Result.GetValue();
            }
         }
      }
   }
}

I am doing this because EPropertyBagPropertyType::Class is unsupported by FGetDataflowVariablesNode (see: FDataflowDynamicConnections::IsSupportedType)

The Problem

FGetDataflowVariableNodes react to a changed value of their owning FDataflowInstance’s Variables via hardcoded functionality in SDataflowMembersWidget:

void SDataflowMembersWidget::OverridesDetailsViewFinishedChangingProperties(const FPropertyChangedEvent& InPropertyChangedEvent)
{
    if (TStrongObjectPtr<UDataflow> DataflowAsset = DataflowAssetWeakPtr.Pin())
    {
       if (IDataflowInstanceInterface* InstanceInterface = GetDataflowInstanceInterface())
       {
          const FDataflowInstance& DataflowInstance = InstanceInterface->GetDataflowInstance();
 
          TMap<FString, int32> PropertyNameStack; // ordered from deeper to 
          InPropertyChangedEvent.GetArrayIndicesPerObject(0, PropertyNameStack);
 
          const FName VariableName = DataflowMembersWidget::Private::ExtractVariableNameFromPropertyChangeEvent(InPropertyChangedEvent);
          InvalidateVariableNode(*DataflowAsset, VariableName);
       }
    }
}
 
void SDataflowMembersWidget::InvalidateVariableNode(const UDataflow& InDataflowAsset, FName InVariableName)
{
    if (InDataflowAsset.Variables.FindPropertyDescByName(InVariableName))
    {
       // invalidate all the get nodes all the nodes that match the variable name 
       for (const TSharedPtr<FDataflowNode>& Node : InDataflowAsset.GetDataflow()->GetNodes())
       {
          if (FGetDataflowVariableNode* VariableNode = Node->AsType<FGetDataflowVariableNode>())
          {
             if (VariableNode->GetVariableName() == InVariableName)
             {
                VariableNode->Invalidate();
             }
          }
       }
    }
}

My Question

Firstly, I understand that Dataflow is an experimental feature, so I don’t expect a futureproof answer.

How would you recommend I implement the same functionality for FMyDataflowNode?

Cheers,

Josh.

Update:

I have resolved this issue by instead using a struct wrapper for TSubclassOf as my Dataflow Variable Type.

This means I no longer need to mimic the behavior of FGetVariableDataflowNode

I would however be interested in the reasoning behind Class and SoftClass being unsupported by Dataflow

Hi,

I’m glad you managed to resolved the issue by using the wrapping struct

I’ll have to check exactly why we do not support those types, but that could be that that we did not anticipated those specific types to be used in Dataflow

I’ll add this to our to do list of things to fix

Actually I’m curious to understand what is your use case in using a TSubClassOf and writing your own node :slight_smile:

Anyway, leveraging the existing variable node is certainly the better more future proof way for now.

A way you could have detected if a variable override value was changed is to listen to FCoreUObjectDelegates::OnObjectPropertyChanged

But you’d get a call for every UObject changed in the editor because this is a global delegate

So you’d need to look at the object passed as a parameter to your handler function and check if it has a dataflow interface and check if the dataflow asset matches the dataflow asset passed in the construction of your node through FNodeParameters ( your node would need to keep a weakObjectPtr of it in the constructor code )

I hope this helps

Heya Cedric,

Many thanks for the answer

My use case is to create a general purpose version of FBlueprintToCollectionDataflowNode, such that I can create a shared Dataflow Asset for many hundreds of Actor Blueprints.

I did notice that FCoreUObjectDelegates::OnObjectPropertyChanged is bound to by the existing variable node, however during testing I found that this doesn’t actually get called when a variable override is changed in a Dataflow Asset, leaving SDataflowMembersWidget::InvalidateVariableNode as the only viable code path/hook.

Cheers,

Josh

yes the property change will only work on the overrides, if you change the actual variables in the asset itself then this won’t fire

we have global notification of variable and subgraph edits at the dataflow level : FDataflowAssetDelegates contains those events

regarding the BlueprintCollection node : Could you provide more details on what is missing from the current one ? it should be quite general purpose as it looks for various type of component in the blueprint and creates a collection out of it

if there’s something missing that could make it even more practical, we could consider updating it or create a new version of the node for that purpose

I was sure that changing an override didn’t fire FGetDataflowVariableNode::OnObjectPropertyChanged, so I went back and checked again. I discovered some odd behavior - The delegate will only fire if the class chosen is currently unloaded. Subsequent selections of that class (select a different class first) don’t fire.

What’s missing from FBlueprintToCollectionDataflowNode:

The Blueprint variable is stored on the node instead of being a node input that can be pulled from the dataflow instance:

[Image Removed]

This makes the node incompatible with per-instance overriding for the blueprint (class) used

That loaded/unloaded class issue is odd effectively

Regarding the blueprint node that should be as easy to expose the Blueprint parameter as a input pin in the node, but I see the issue that variables do not support blueprint types somehow

I’ll have a look to see if we can support those types more natively