Exploring the “FoldProperty” specifier
When programming AnimNodes in Unreal Engine, you might encounter an interesting meta-specifier in some UPROPERTY-fields: FoldProperty.
Interestingly, while there have been a few (very few) community contributions on this topic, it seems to have never been officially documented. How unexpected.
So what does this do, how do we use it, and how does it work?
What is a FoldProperty?
Let’s get this one out of the way first.
The general Idea behind this meta-specifier is that we can save on memory if an AnimNode is instantiated a lot. How, you may ask? By removing some of the member variables of the node and treating them as constants. Because if they are constant, we don’t need every instance of an AnimNode to hold that value. Instead we only need one instance of that node for all Animation Blueprints of a given class. And each of the AnimNodes within these Animation Blueprints can then read that value from the same location.
For a simple boolean variable, saving one byte is likely not going to be worth it, but once your AnimNode grows it may become worth saving some memory. Especially if there are a lot of instance of that node running in the scene(either because you have a lot of characters, or just a few characters that simply use your node a lot).
For what it is though, it is a fairly simple and straight forward optimization if you know your value may be a constant, so there rarely is much of an argument against using this tool at your disposal.
How do we use it?
This one’s quick.
First, you declare your variable as usual with the only difference being that it needs to be guarded by an #if WITH_EDITORONLY_DATA preprocessor guard.
This means that in the editor, our property will be available as usual, but in a shipped build it will simply not exist, as it is compiled out.
/** Within FMyAnimNode */
#if WITH_EDITORONLY_DATA
UPROPERTY(EditAnywhere, meta = (FoldProperty, PinHiddenByDefault))
FTransform MySampleProperty;
#endif //WITH_EDITORONLY_DATA
But if it won’t be available in a shipped build, is our data simply lost in the final game? Nay! We just need to trust Unreal to keep it safe and ask nicely. So let’s create a getter method we can use to obtain our folded properties value.
We’ll do this using the GET_ANIM_NODE_DATA macro which we feed the type and name of our variable and that’s it. Wrapped up in a nice getter function, marked as [[nodiscard]] and const for cleanliness and correctness and this is what we end up with:
/** Within FMyAnimNode */
[[nodiscard]] FTransform FMyAnimNode::GetMySampleProperty() const
{
return GET_ANIM_NODE_DATA(FTransform, MySampleProperty);
}
And we can use it anywhere in our AnimNode, so any time we would read MySampleProperty, we will now use GetMySampleProperty() instead.
And since our getter is not guarded by preprocessor statements, it will also be available in a shipped build.
If we were to forget to use the getter over direct variable access, that previous preprocessor guard will not even let us compile the project. This means that even if we were to forget we have a getter, our own design will force us to use the correct way. Neat!
Anything else? Nope! If all you care about is what it does and how to use it, you are good to go now. But if you are curious, let’s take a look at…
How it works
If we search the engine source code for the string literal "FoldProperty", we will encounter it being used within the Animation Blueprint Compiler. More specifically FAnimBlueprintCompilerContext::GatherFoldRecordsForAnimationNode.
And the two usecases we find both lead to FAnimBlueprintCompilerContext::AddFoldedPropertyRecord if our variable has that meta specifier, which is determines whether a given property on an animation blueprint is actually constant by definition, or whether it might actually change.
And how does the compiler know if a property is constant or not? Well, there are a few conditions.
- If that property is marked as a “Dynamic Value” in the AnimNodes details panel, it is assumed to be mutable, as it’s data can change at any point,
- otherwise, if the property is not exposed as a pin, it is treated as a constant,
- otherwise, if the property is exoposed as a pin, but that pin is not connected, it is also treated as a constant. In all other cases, the property is regarded as “mutable”, meaning it could change at any time and thus cannot be folded.
AddFoldedPropertyRecord will also add an error to the compilation result, if our variable is not an editor-only property, so we don’t even need to wait until our build fails to notice an error.
But assuming we did our job, and he prerequisites are met, the AnimBlueprint compiler will end up calling FAnimBlueprintCompilerContext::ProcessFoldedPropertyRecords, which will end up adding our constant variables to UAnimBlueprintGeneratedClass::AnimNodeData, which is an Array of FAnimNodeData. This array is one entry, per AnimNode and once compilation is done, each AnimNode will receive a pointer to their data entry.,
Alrighty, now we know how Unreal folds our constant data into the compiled UAnimBlueprintGeneratedClass. How do we get it back?
Using the GET_ANIM_NODE_DATA macro like we did in our example above, we essentially resolve to a call to FAnimNodeData::GetData via FAnimNode_Base::GetData<>().
This takes an UE::Anim::FNodeDataId, which is based on the identifier( read “name”) of our variable and gets automatically passed into the function by the macro.
From here on, it is more straight forward. FAnimNodeData has an array called Entries, and the NodeDataId the macro passes into this function contains an index into these Entries.
The function then uses a bit mask to determine whether that the entry for the property we are requesting is mutable or constant and returns the corresponding mutable or constant data that got compiled into the UAnimBlueprintGeneratedClass beforehand.
The only other notable thing is that FAnimNodeData::GetData() actually just returns void*, so that gets casted to the correct type in FAnimNode_Base::GetData<>() which then returns to our original callsite in the AnimNode and we continue with our AnimNode code.
And because the macro uses a protected method on the AnimNode baseclass and passes in the this pointer, it is only usable within an AnimNode.
And that is it!
Now we know what the FoldProperty meta specifier does, how we can use it, and have at least an overview over how it is actually implemented in the engine.
I hope you learned something. I sure did, as exploring the depths of the engine always brings up interesting new nuggets of knowledge, no matter how often you have already done so.
And if you are really curious now and didn’t know about this topic before, I encourage you to see if you can track the flow I have described here yourself in the engine.