UE5 アニメーションのマルチスレッド処理についてのメモ

# 概要
UnrealEngine5のアニメーションでのマルチスレッド処理についてのメモ書きです。
BPと(`PropertyAccess`)と、C++の両方を試してみます。


## 更新履歴
| 日付 | 内容 |
|:-----|:----|
|2023/04/04| 初版 |


## 参考
以下の記事を参考にいたしました、ありがとうございます。
[UE公式：アニメーションの最適化](https://docs.unrealengine.com/5.1/ja/animation-optimization-in-unreal-engine/)
[UE公式：UAnimInstance](https://docs.unrealengine.com/5.1/en-US/API/Runtime/Engine/Animation/UAnimInstance/)
[猫でも分かる UE5.0, 5.1 におけるアニメーションの新機能について](https://www.docswell.com/s/EpicGamesJapan/ZY3PDK-UE_CEDECKYUSHU2022_UE5Animation#p135)
[UE5 Property Accessを使ってAnimBPをマルチスレッドに更新しよう](https://unrealengine.hatenablog.com/entry/2022/06/19/180000)
[スレッドセーフという幻想と現実](https://yohhoy.hatenablog.jp/entry/2013/12/15/204116)

### 関連する過去記事
[UE4 アニメ―ションBPをC++クラス継承にしてアクセスする](https://qiita.com/unknown_ds/items/337765997e72e425227b)
[UE4 非同期処理についてのメモ](https://qiita.com/unknown_ds/items/72c910888877995b3a85)

## 環境
Windows10
Visual Studio 2022
UnrealEngine 5.1.1


## 関連ソース
"\Engine\Source\Runtime\Engine\Classes\Animation\AnimInstance.h"
"\Engine\Source\Runtime\Engine\Private\Animation\AnimInstance.cpp"
"\Engine\Source\Runtime\Engine\Public\Animation\AnimInstanceProxy.h"


# ブループリントでの実装方法
UE5からアニメーションBPでマルチスレッド処理ができるようになったようです。

## BlueprintThreadSafeUpdateAnimation を使う
この関数はワーカースレッドで実行されアニムグラフの更新直前に実行されるようです。

![BpThreadSafeUpdateAnimation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c459851d-0207-85af-a5ae-12ec6bad8098.png)


ただしこの関数でスレッドセーフ以外の関数ノードを使用すると以下のようにエラーになります。
<font color="#ff1111">TryGetOwner や GetMovementComponent がスレッドセーフではないと警告がでています。</font>

![BP1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/92a6f673-4d28-1396-f9df-a214f917f93c.png)

この場合、右クリック[変数]の[プロパティアクセス]から
![Menu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/469faafb-471e-d97e-819e-17ecf5d501e7.png)

対象の関数を選び
![ProertyAccess.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/9bdd42b3-0d8e-1900-b5ef-7cd6092b383e.png)

差し替えることでエラーがでなくなります。
![BP2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/be6a98cd-4503-b8c0-72a6-a2426e4f72d2.png)

UE5にて追加された **PropertyAccess** という機能にて外部オブジェクトをローカル変数にコピーしてくれていることによりスレッドセーフを実現しているようです。
また自作関数の場合は、[詳細設定]より[スレッドセーフ]にチェックをいれることで `BlueprintThreadSafeUpdateAnimation` での使用が可能となりますが、実際にスレッドセーフであるかの責任は作成者が負う事になります。
![ThreadSafeOption.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/e589a6e0-bd6b-c783-09b1-dcf3dd09ef74.png)

C++での自作関数の場合はメタ情報に `BlueprintThreadSafe` の付与が必要です。これも実際にスレッドセーフ実装の責任は作成者が負うことになります。
```cpp:スレッドセーフ情報の付与
	UFUNCTION(BlueprintCallable, meta=(BlueprintThreadSafe))
	void HeavyFunc(){ /* スレッドセーフな処理 */ }
```


# C++ での実装方法
C++ではUE4時代からある`FAnimInstanceProxy`を使う方法と、UE5から追加された`NativeThreadSafeUpdateAnimation`を使う方法の2通りがあるようです。

## NativeThreadSafeUpdateAnimation を使う
C++では `UAnimInstance::NativeThreadSafeUpdateAnimation` でワーカースレッドにて実行することができます。スレッドセーフではない外部のオブジェクトにアクセスする場合などは排他処理を行うか、 `UAnimInstance::NativeUpdateAnimation` にてローカルにコピーしておく等の対応が必要になります。

以下コード例。

```cpp:MyAnimInstance.h
UCLASS()
class MYPROJECT_API UMyAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
	
public:
	// ネイティブ更新オーバーライドポイント。
	virtual void NativeUpdateAnimation(float _DeltaSeconds) override;

	// ネイティブスレッドセーフ更新オーバーライド ポイント。
	virtual void NativeThreadSafeUpdateAnimation(float _DeltaSeconds) override;

	// ネイティブ ポスト評価オーバーライドポイント
	virtual void NativePostEvaluateAnimation() override;

private:
	FVector	LocalVelocity;
};

```

```cpp:MyAnimInstance.cpp
// ネイティブ更新
void UMyAnimInstance::NativeUpdateAnimation(float _DeltaSeconds)
{
	// 基底処理
	Super::NativeUpdateAnimation(_DeltaSeconds);
	
	// スレッドセーフではない処理
	const APawn* _Pawn = Cast<APawn>(TryGetPawnOwner());
	if (!_Pawn){
		return;
	}

	UCharacterMovementComponent* MoveComp = Cast<UCharacterMovementComponent>(_Pawn->GetMovementComponent());
	LocalVelocity = MoveComp->Velocity;
}

// ネイティブスレッドセーフ更新
void UMyAnimInstance::NativeThreadSafeUpdateAnimation(float _DeltaSeconds)
{
	// 基底処理
	Super::NativeThreadSafeUpdateAnimation(_DeltaSeconds);

	// ワーカースレッドでの処理
}

// ネイティブアニメーション評価後
void UMyAnimInstance::NativePostEvaluateAnimation()
{
	// 基底処理
	Super::NativePostEvaluateAnimation();
	
	// 外部オブジェクトへの更新など
}
```


## AnimInstanceProxy を使う
UE4時代からあった方法として、`FAnimInstanceProxy` を使った方法があります。`FAnimInstanceProxy`は UAnimInstance の代わりにアニメーション更新中に渡されるプロキシオブジェクトです。

`FAnimInstanceProxy`を継承して差し替え、スレッドセーフ処理のために外部オブジェクトからのデータを `PreUpdate` で保持し、`Update`でワーカースレッドでの処理を書きます。
以下コード例。

```cpp:MyAnimInsntance.h
// アニムインスタンスプロクシ
USTRUCT()
struct MYPROJECT_API FMyAnimInstanceProxy : public FAnimInstanceProxy
{
	GENERATED_USTRUCT_BODY()
	
public:
	virtual void InitializeObjects(UAnimInstance* _InAnimInstance) override;

	virtual void PreUpdate(UAnimInstance* _InAnimInstance, float _DeltaSeconds) override;

	virtual void Update(float _DeltaSeconds) override;
	
	virtual void PostUpdate(UAnimInstance* _InAnimInstance) const override;


	// 収集データ
	UPROPERTY(Transient)
	TObjectPtr<APawn> Owner;

	UPROPERTY(Transient)
	FVector ActorLocation;

	UPROPERTY(Transient)
	TObjectPtr<UCharacterMovementComponent> MovementComponent;

	UPROPERTY(Transient)
	FVector Velocity;;

	UPROPERTY(Transient)
	bool bIsAnyMontagePlaying;
};

// アニメーションインスタンスクラス
UCLASS()
class MYPROJECT_API UMyAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

private:
	UPROPERTY(Transient)
	FMyAnimInstanceProxy Proxy;

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

	virtual void DestroyAnimInstanceProxy(FAnimInstanceProxy* InProxy) override {}

	friend struct FMyAnimInstanceProxy;
};
```

```cpp:MyAminInsntace.cpp
void FMyAnimInstanceProxy::InitializeObjects(UAnimInstance* _InAnimInstance)
{
	FAnimInstanceProxy::InitializeObjects(_InAnimInstance);

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

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

}

void FMyAnimInstanceProxy::PreUpdate(UAnimInstance* _InAnimInstance, float _DeltaSeconds)
{
	FAnimInstanceProxy::PreUpdate(_InAnimInstance, _DeltaSeconds);

	// 外部からデータ収集
	if (::IsValid(Owner)){
		ActorLocation = Owner->GetActorLocation();
	}
	if (::IsValid(MovementComponent)){
		Velocity = MovementComponent->Velocity;
	}
	if (_InAnimInstance){
		bIsAnyMontagePlaying = _InAnimInstance->IsAnyMontagePlaying();
	}
}

void FMyAnimInstanceProxy::Update(float _DeltaSeconds)
{
	FAnimInstanceProxy::Update(_DeltaSeconds);

	// ワーカースレッドの処理
}

void FMyAnimInstanceProxy::PostUpdate(UAnimInstance* _InAnimInstance) const
{
	FAnimInstanceProxy::PostUpdate(_InAnimInstance);

	// 外部オブジェクトへの更新
}
```

Proxyも継承する必要があるので少し煩雑です。あと、UE5から追加された `NativeThreadSafeUpdateAnimation`を使う方法が推奨されているようです。


# まとめ
BPのみでマルチスレッド処理の実装ができるのはC++での実装にかかる難易度を考えるとかなり凄い機能だと思います。
スレッドセーフ処理については、アニメーション処理上では外部オブジェクトの参照がほとんどだと思われるので排他処理ではなくコピーしておくパターンが多いのではないかと思います。