Output struct reference in blueprint node sometimes retains reference from previous call in a foreach loop.

I occasionally encounter a scenario where a blueprint node, defined in C++ with an struct reference parameter (as output), when used in blueprints as node in a for-each loop, will retain the reference to the struct from the previous iteration of the loop.

I’ve tried to create a mock-up of the code here:

`USTRUCT(BlueprintType)
FSomeStruct
{
GENERATED_BODY()

UPROPERTY(BlueprintReadOnly)
TArray subStructs;
}

UCLASS(BlueprintType)
class USomeSubSystem : UWorldSubsystem
{
GENERATED_BODY()

UFUNCTION(BlueprintCallable, BlueprintPure=false)
static bool BuildUpSomeStruct(FSomeStruct& outStruct);
}`

When I setup blueprint logic to call this function in a for-each loop on a collection of FSomeStructs, then attach a debugger and breakpoint in the function, it will sometimes* have the reference to the result from the previous iteration loaded when calling in subsequent iterations. This is especially noticeable because the struct has an array property, which can be seen to have entries in it. The passed reference isn’t to a duplicate instance of the struct; If I clear the array on the second iteration, I can observe the first iterations result will be cleared as well.

*Sometimes: It doesn’t happen for every node that follows this pattern, and more disturbingly, for each instance of the same node.

Is this expected or known behaviour, and is there a good workaround? Passing the struct as a return value does avoid the issue but the struct can grow quite big so I would like to avoid it being copied.

Good day, first of all: output pins of blueprint nodes in event graph for-loops are known to keep their value from last iteration. The same applies to nodes in Event Graphs that are otherwise called multiple times like execution looping back via Exec pin connections. It boils down to the fact that Event Graphs just have a persistent state. The usual workaround for that is to move the nodes from the event graph into a function graph which doesn’t have a persistent state since the local variables of a function graph get initialized when the function is entered and destroyed when it’s exited.

If I understand your report correctly the problem is that when you access outStruct inside the C++ function, it will even have the previous iteration’s state, is that correct? I can see that makes for a confusing workflow, but I don’t expect us to change that behavior soon. It would be addressed if “in-out” function parameters like FSomeStruct& get re-initialized on entering the function but we may want to avoid both (1) that cost and (2) fallout from existing projects that may depend on the current behavior. If possible, I recommend moving that BP logic into a function graph. Would that help in your case?

In my existing blueprint, both the foreach-loop and the function node are in the same function graph, but if I understand what you’re saying correctly, it would not retain the previous iteration’s state if I were to nest the function node in another function graph? If so, that definitely works for me!

Yeah I can imagine you want to keep it backwards compatible. I think in my ideal world there’d be a UPARAM specifier to indicate prior state of the parameter should be cleared or not. But of course I don’t have enough insight into the workings of those specifiers to know if that’s a viable approach.

Thanks for the help!

“In my existing blueprint, both the foreach-loop and the function node are in the same function graph, but if I understand what you’re saying correctly, it would not retain the previous iteration’s state if I were to nest the function node in another function graph? If so, that definitely works for me!”

Aha - the same I mentioned about event graph output pin states persisting across frames also applies to function graphs while inside that graph yes! So I expect moving the node with that output pin into another function graph while keeping the for-loop outside of that should be an effective workaround. Can you let us know if that solved the issue?

A UPARAM way to reset a parameter-by-ref would be useful and low risk, that’s a nice idea. I’ll suggest this to the blueprint team.

Yes, that resolved it, thanks again for the help!

(Late reply since Epic’s offices were closed.)

Glad to hear that! I’ll close this.