Animation Property Access System in C++?

I’m attempting to move some Anim Blueprint logic over to a base C++ Anim Instance class, but can not figure out how to use the new Threadsafe Property Access System in C++.

There is no documentation on how to do this. My Anim Blueprint is making extensive use of the property access node, can anybody give a simple example of the equivalent in c++?

Thank you.

Hi! There is an example of how to implement custom native AnimInstance class using the new FAnimInstanceProxy

Hey, thank you. I actually just read this, but it’s not clear if this proxy is the same thing that is being done in the lyra example? I’m doing what they are doing and using the BlueprintThreadSafeUpdateAnimation function which accesses values using the propery access system node.

It’s has been easy moving most of the blueprint logic to my c++ anim instance class until I need to access a property that is using this node:

Are you saying the equivalent of this blueprint node in c++ is to use the anim proxy struct? Will it be safe to access proxy variables in my c++ anim instance thread safe functions?? If so do you know where I can find a better example then the docs linked? I have not been able to find anythign besides that doc, no forum posts, questions, blogs, etc…

Just to be clear, all I’m trying to do is “TryGetPawnOwner.GetActorLocation” in my c++ thread safe function. In blueprints it won’t let you make that function call unless you use the property access node, whereas in c++ im not sure how to do this.

If I was to follow that doc, its suggesting I create a proxy struct, update the GetActorLocation variable like I normal would (Just calling TryGetPawnOwner->GetActorLocation()) inside the proxy update, then back in the c++ anim instance function get the proxy to access the variable?

Should I move all my c++ anim instance variables over to the proxy? or just the variables that are using the property access system nodes (thread safe variables)?

Btw TryGetPawnOwner is not accessible from the Proxy. I’m guessing the correct way to get that would be to set an owner variable on the proxy during its initialization becuase it passes in the Anim Instance, and accessing the GetActorLocation() from that? Assuming everything else I said is correct.

Hi, sorry for so long answer. To be clear, I’m not an Animation Developer. But I’m also interested in this topic, maybe it would be easier for us to cooperate))

Just to be clear, all I’m trying to do is “TryGetPawnOwner.GetActorLocation” in my c++ thread-safe function. In blueprints it won’t let you make that function call unless you use the property access node, whereas in c++ im not sure how to do this.

If I understood correctly, this won’t be a thread-safe call, cause you’re trying to get info directly from Owner, outside of AnimInstance. That’s why EventGraph is empty in AnimBP. As they stating in doc about animation in Lyra:

“When using Thread Safe functions you can not access data directly from game objects as you could in the Event Graph. For example, if you were attempting to copy the Gameplay float value for the Character’s speed it would not be considered thread-safe, therefore we recommend the use of Property Access to accommodate these instances.”

I think you should use proxy stored variables only for property access.

I’m guessing the correct way to get that would be to set an owner variable on the proxy during its initialization becuase it passes in the Anim Instance, and accessing the GetActorLocation() from that?

I would do that. Maybe even tried to set all properties on init that I would need to have access to in future, like Location, Rotation, Velocity etc.

By the way, just run a simple test measuring exec time for different AnimBPs in the game thread.
ALS_AnimBP ≈ 180μs
Basic AnimBP (only owners rotation and velocity) ≈ 80μs
Lyra AnimBP ≈ 30μs
Measured by Unreal Insights. The test wasn’t perfect, just simple AIBot running and jumping through the level, but still interesting results. Maybe I should run a test with multiple bots to make a stress test.

Thank you for your input. Those are interesting test results, I was worried about performance as it seems like it would be worse using the proxy as it’s causing me to have some duplicate variables that already exists on my anim instance.

But as you said, the proxy is the only way to go. If you attempt to access properties outside the anim instance it will crash at runtime. Using the proxy I have not had any issues. Although its still not clear if I’m doing everything correctly, but I was able to get this working. (At least it appears to be).

The docs are actually incorrect, it wont compile if you copy what they do. Below is a simplified example of what I found to work. I also removed the proxy from being accessible in blueprints as it makes no sense because you can just use the property access node in blueprints.

Anim Instance Header:

#include "Animation/AnimInstanceProxy.h"

USTRUCT()
struct FCoreAnimInstanceProxy : public FAnimInstanceProxy
{
    GENERATED_BODY()

	virtual void InitializeObjects(UAnimInstance* InAnimInstance) override;

	virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override;

    virtual void Update(float DeltaSeconds) override;

public:

	// Owner Data
	UPROPERTY(Transient)
	APawn* Owner;

	UPROPERTY(Transient)
	FVector ActorLocation;

	// Movement Component Data
	UPROPERTY(Transient)
	UCharacterMovementComponent* MovementComponent;

	UPROPERTY(Transient)
	FVector CurrentAcceleration;

	// Anim Instance Data
	UPROPERTY(Transient)
	bool bIsAnyMontagePlaying;
};

UCLASS()
class COREV5_API UCoreAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

public:
	
	UCoreAnimInstance(const FObjectInitializer& ObjectInitializer);

	virtual void NativeThreadSafeUpdateAnimation(float DeltaSeconds) override;

private:

	///////////////////////////////////////////////////
	// Thread Safe Proxy
	///////////////////////////////////////////////////

    UPROPERTY(Transient)
    FCoreAnimInstanceProxy Proxy;

    virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override { return &Proxy; }

    virtual void DestroyAnimInstanceProxy(FAnimInstanceProxy* InProxy) override {}

    friend struct FExampleAnimInstanceProxy;
};

Anim Instance Cpp:

void FCoreAnimInstanceProxy::InitializeObjects(UAnimInstance* InAnimInstance)
{
	FAnimInstanceProxy::InitializeObjects(InAnimInstance);

	Owner = InAnimInstance->TryGetPawnOwner();
	if (Owner == nullptr) { return; }

	MovementComponent = Cast<UCharacterMovementComponent>(Owner->GetMovementComponent());
}


void FCoreAnimInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds)
{
	FAnimInstanceProxy::PreUpdate(InAnimInstance, DeltaSeconds);
	if (InAnimInstance)
	{
		bIsAnyMontagePlaying = InAnimInstance->IsAnyMontagePlaying();
	}

	if (Owner)
	{
		ActorLocation = Owner->GetActorLocation();
	}

	if (MovementComponent)
	{
		CurrentAcceleration = MovementComponent->GetCurrentAcceleration();
	}
}


void FCoreAnimInstanceProxy::Update(float DeltaSeconds)
{
	FAnimInstanceProxy::Update(DeltaSeconds);
}

Just for clarity for future readers, to access an updated property of the proxy, in your anim instance you simply ask the proxy:

Location = Proxy.ActorLocation;

Although there is still many questions about this proxy and the “correct” way to use it:

  1. When to use PreUpdate, Update, or PostUpdate to update variables? I am currently doing everything with PreUpdate as it gives me access to the InAnimInstance.

  2. Can I use the proxy to fill the variables directly on my anim instance instead of having a copy variable on the proxy that is being filled?

  3. Can I access something on the Anim Instance directly in a thread safe function without the proxy? Like bIsAnyMontagePlaying.

  4. Is it safe to use InitializeObjects to cast to the correct anim instance, pawn owner, and movement component, and stores those values on the proxy without issues, or should we just grab, cast, and check for null each time from the InAnimInstance on PreUpdate?

4 Likes

Thank you for the research that you’ve done! Great job!
Sorry, I can’t help you with those questions, they are outside of my level of expertise, but I wish I could(
Saved your post, it will definitely help me in the future!

  1. When to use PreUpdate, Update, or PostUpdate to update variables? I am currently doing everything with PreUpdate as it gives me access to the InAnimInstance.

[Documentation]

Data must be exchanged with the proxy for each tick (either through buffering, copying or some other strategy) in FAnimInstanceProxy::PreUpdate or FAnimInstaceProxy::PreEvaluateAnimation. Any data that then needs to be accessed by external objects should then be exchanged/copied from the proxy in FAnimInstanceProxy::PostUpdate.

I’m interpreting this to mean that assigning your proxy’s variables from your AnimInstance happens at PreUpdate, making changes to those variables happens in Update, and assigning those updated values back to your AnimInstance happens in PostUpdate.

  1. Can I use the proxy to fill the variables directly on my anim instance instead of having a copy variable on the proxy that is being filled?

I guess you would fill in your AnimInstance’s variables during PostUpdate.

  1. Can I access something on the Anim Instance directly in a thread safe function without the proxy? Like bIsAnyMontagePlaying

My thoughts are that you can access (read) any variable you want from your AnimInstance like normal, it’s just that you can’t write to them (outside of the proxy).

  1. Is it safe to use InitializeObjects to cast to the correct anim instance, pawn owner, and movement component, and stores those values on the proxy without issues, or should we just grab, cast, and check for null each time from the InAnimInstance on PreUpdate?

I think you still need to null-check, because this is data that you’re feeding into PreUpdate, and I’m not sure that there’s a guarantee that the references are valid; though, I would assume this not to be unlike any other game code you write, where you can get away without null-checking if you’ve guaranteed validity in other ways.

I hope we can get an actual Unreal rep in here to explain these things, but I think we’re getting close!

1 Like

Thank you for that clarification. It’s as I was expecting. I agree, it would be nice to get more details from epic.

did you know how to make a property access inside c++ ?

Instead of doing any of this proxy stuff, cant we just use NativeThreadSafeUpdateAnimation override?

If you look at lyra, all the calculations are done in BlueprintThreadSafeUpdateAnimation . So following the same pattern cant we just make normal BP read-only Uproperties in UAnimInstance in c++. Do calculations and set those value in NativeThreadSafeUpdateAnimation override function?

If you peak NativeThreadSafeUpdateAnimation definition , the comment says “// Native thread safe update override point. Executed on a worker thread just prior to graph update
// for linked anim instances, only called when the hosting node(s) are relevant”
So from what I understand, this function 1) runs on a worker thread 2) Runs before anim graph thus making all our calculations available to use.

As far as accessing external values such as character world position or acceleration or velocity etc goes, those are being read not modified. So there shouldn’t be any problem there.

So like do you even need a property access system equivalent in c++? One problem that I can think of is if worker thread ran more than once before the main thread then that would result in
a property having same value over multiple ticks.
One hack that I can think of is I guess use a bool initialize it to false. Make it true every tick in NativeUpdateAnimation. Then in NativeThreadSafeUpdateAnimation check if the bool is true.
If true then do calculations and set it to false, if bool is false then skip this tick. This way we know that main thread has had a chance to run before worker thread.

I cannot overstate the importance of reading and understanding this document. I’m fairly experienced in this area, but I was completely not involved in Unreal when the switch to multi-threaded animations occurred, so I completely missed this information.

This should be required reading for anyone working with ABPs.